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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
import os
import pytest
from pathlib import Path
from bs4 import BeautifulSoup
import docutils
import sphinx
from sphinx_tabs.tabs import JS_FILES, CSS_FILES
pytest_plugins = "sphinx.testing.fixtures"
def pytest_configure(config):
config.addinivalue_line(
"markers", "noautobuild: mark test to prevent autouse fixtures from running"
)
@pytest.fixture(scope="session")
def rootdir():
"""Pytest uses this to find test documents."""
return Path(__file__).parent.absolute() / "roots"
@pytest.fixture(autouse=True)
def auto_build_and_check(
app,
status,
warning,
check_build_success,
get_sphinx_app_doctree,
regress_sphinx_app_output,
request,
):
"""
Build and check build success and output regressions.
Currently all tests start with this.
Disable using a `noautobuild` mark.
"""
if "noautobuild" in request.keywords:
return
app.build()
check_build_success(status, warning)
get_sphinx_app_doctree(app, regress=True)
regress_sphinx_app_output(app)
@pytest.fixture()
def manual_build_and_check(
app,
status,
warning,
check_build_success,
get_sphinx_app_doctree,
regress_sphinx_app_output,
):
"""
For manually triggering app build and check.
"""
app.build()
check_build_success(status, warning)
get_sphinx_app_doctree(app, regress=True)
regress_sphinx_app_output(app)
@pytest.fixture
def check_build_success():
"""Check build is successful and there are no warnings."""
def check(status, warning):
assert "build succeeded" in status.getvalue()
warnings = warning.getvalue().strip()
assert warnings == ""
return check
@pytest.fixture
def regress_sphinx_app_output(file_regression, get_sphinx_app_output):
"""
Get sphinx HTML output and regress inner body (other sections are
non-deterministic).
"""
def read(app, buildername="html", filename="index.html", encoding="utf-8"):
content = get_sphinx_app_output(app, buildername, filename, encoding)
if buildername == "html":
soup = BeautifulSoup(content, "html.parser")
# Remove output from ``pygments``, so that test only compares HTML of surrounding tabs
for div in soup.find_all("div", {"class": "highlight"}):
div.decompose()
if sphinx.version_info < (8, 1):
body = soup.select("div.body")[0]
body.append(soup.new_tag("div", **{"class": "clearer"}))
doc_div = soup.findAll("div", {"class": "documentwrapper"})[0]
doc = doc_div.prettify()
else:
doc = content
file_regression.check(
doc, extension="." + filename.split(".")[-1], encoding="utf8"
)
return read
@pytest.fixture
def get_sphinx_app_doctree(file_regression):
"""Get sphinx doctree and optionally regress."""
def read(app, docname="index", resolve=False, regress=False, replace=None):
if resolve:
doctree = app.env.get_and_resolve_doctree(docname, app.builder)
extension = ".resolved.xml"
else:
doctree = app.env.get_doctree(docname)
extension = ".xml"
# convert absolute filenames
for node in doctree.findall(lambda n: "source" in n):
node["source"] = Path(node["source"]).name
if regress:
text = doctree.pformat() # type: str
for find, rep in (replace or {}).items():
text = text.replace(find, rep)
if sphinx.version_info < (7, 1):
text = text.replace(
'<document source="index.rst">',
"<document source=\"index.rst\" translation_progress=\"{'total': 0, 'translated': 0}\">",
)
if docutils.__version_info__ < (0, 22):
text = text.replace('="False"', '="0"')
text = text.replace('linenos="True"', 'linenos="1"')
file_regression.check(text, extension=extension)
return doctree
return read
@pytest.fixture
def check_asset_links(get_sphinx_app_output):
"""
Check if all stylesheets and scripts (.js) have been referenced in HTML.
Specify whether checking if assets are ``present`` or not ``present``.
"""
def check(
app,
buildername="html",
filename="index.html",
encoding="utf-8",
cssPresent=True,
jsPresent=True,
):
content = get_sphinx_app_output(app, buildername, filename, encoding)
soup = BeautifulSoup(content, "html.parser")
stylesheets = soup.find_all("link", {"rel": "stylesheet"}, href=True)
css_refs = [s["href"] for s in stylesheets]
scripts = soup.find_all("script", src=True)
js_refs = [s["src"] for s in scripts]
all_refs = css_refs + js_refs
if cssPresent:
css_present = all(any(a in ref for ref in all_refs) for a in CSS_FILES)
assert css_present
else:
assert not "sphinx_tabs" in css_refs
if jsPresent:
js_present = all(any(a in ref for ref in js_refs) for a in JS_FILES)
assert js_present
else:
assert not "sphinx_tabs" in js_refs
return check
@pytest.fixture()
def get_sphinx_app_output():
"""Get content of a sphinx app output file."""
def get(app, buildername="html", filename="index.html", encoding="utf-8"):
outpath = Path(app.srcdir) / "_build" / buildername / filename
if not outpath.exists():
raise IOError("No output file exists: {}".format(outpath.as_posix()))
return outpath.read_text(encoding=encoding)
return get
|