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
|
"""
Uses sphinx's pytest fixture to run builds
usage:
.. code-block:: python
@pytest.mark.sphinx(
buildername='html',
srcdir='path/to/source')
def test_basic(app, status, warning, get_sphinx_app_output):
app.build()
assert 'build succeeded' in status.getvalue() # Build succeeded
warnings = warning.getvalue().strip()
assert warnings == ""
output = get_sphinx_app_output(app, buildername='html')
parameters available to parse to ``@pytest.mark.sphinx``:
- buildername='html'
- srcdir=None
- testroot='root' (only used if srcdir not set)
- freshenv=False
- confoverrides=None
- status=None
- warning=None
- tags=None
- docutilsconf=None
"""
import os
import pathlib
import shutil
import pytest
from bs4 import BeautifulSoup
from docutils import nodes
from myst_parser._compat import findall
SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "sourcedirs"))
@pytest.fixture(scope="session", autouse=True)
def remove_sphinx_builds():
"""remove all build directories from the test folder"""
yield
srcdirs = pathlib.Path(SOURCE_DIR)
for entry in srcdirs.iterdir(): # type: pathlib.Path
if entry.is_dir() and entry.joinpath("_build").exists():
shutil.rmtree(str(entry.joinpath("_build")))
@pytest.fixture
def get_sphinx_app_output(file_regression):
def read(
app,
buildername="html",
filename="index.html",
encoding="utf-8",
regress_html=False,
regress_ext=".html",
replace=None,
):
outpath = pathlib.Path(str(app.srcdir), "_build", buildername, filename)
if not outpath.exists():
raise OSError(f"no output file exists: {outpath}")
content = outpath.read_text(encoding=encoding)
if regress_html:
# only regress the inner body, since other sections are non-deterministic
soup = BeautifulSoup(content, "html.parser")
doc_div = soup.findAll("div", {"class": "documentwrapper"})[0]
# pygments 2.11.0 introduces a whitespace tag
for pygment_whitespace in doc_div.select("pre > span.w"):
pygment_whitespace.replace_with(pygment_whitespace.text)
# something changed in sphinx 8 (or new docutils) to introduce this, although I couldn't find the actual commit,
# but in any case, it's not important for the regression test
for clearer_div in doc_div.findAll("div", {"class": "clearer"}):
clearer_div.decompose()
text = doc_div.prettify()
# changed in sphinx 7.2
text = text.replace('"Link to this', '"Permalink to this')
for find, rep in (replace or {}).items():
text = text.replace(find, rep)
file_regression.check(text, extension=regress_ext, encoding="utf8")
return content
return read
@pytest.fixture
def get_sphinx_app_doctree(file_regression):
def read(
app,
docname="index",
resolve=False,
regress=False,
replace=None,
rstrip_lines=False,
regress_ext=".xml",
):
if resolve:
doctree = app.env.get_and_resolve_doctree(docname, app.builder)
extension = f".resolved{regress_ext}"
else:
doctree = app.env.get_doctree(docname)
extension = regress_ext
# convert absolute filenames
for node in findall(doctree)(
lambda n: "source" in n and not isinstance(n, str)
):
node["source"] = pathlib.Path(node["source"]).name
doctree = doctree.deepcopy()
# remove attrs added in sphinx 7.1
doctree.attributes.pop("translation_progress", None)
for node in findall(doctree)(nodes.Element):
node.attributes.pop("translated", None)
if regress:
text = doctree.pformat() # type: str
for find, rep in (replace or {}).items():
text = text.replace(find, rep)
if rstrip_lines:
text = "\n".join([li.rstrip() for li in text.splitlines()])
file_regression.check(text, extension=extension)
return doctree
return read
|