-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add HTML tests to avoid regressions (#33)
Essentially, copy the HTML test approach from the Sphinx project.
- Loading branch information
1 parent
3a5d005
commit f60fd4d
Showing
8 changed files
with
664 additions
and
370 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
"""Test HTML output the same way that Sphinx does in test_build_html.py.""" | ||
import re | ||
from itertools import chain, cycle | ||
from pathlib import Path | ||
from typing import Dict | ||
|
||
import pytest | ||
from docutils import nodes | ||
from lxml import etree as lxmltree | ||
from sphinx.testing.path import path as sphinx_path | ||
from sphinx.testing.util import SphinxTestApp | ||
|
||
pytest_plugins = "sphinx.testing.fixtures" | ||
|
||
etree_cache: Dict[str, str] = {} | ||
|
||
|
||
@pytest.fixture(scope='session') | ||
def rootdir(): | ||
return sphinx_path(__file__).parent.abspath() / 'roots' | ||
|
||
|
||
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: | ||
assert self.app.env is not None | ||
doctree = self.app.env.get_doctree(docname) | ||
if post_transforms: | ||
self.app.env.apply_post_transforms(doctree, docname) | ||
return doctree | ||
|
||
|
||
@pytest.fixture(scope='module') | ||
def cached_etree_parse(): | ||
def parse(fname): | ||
if fname in etree_cache: | ||
return etree_cache[fname] | ||
with (fname).open('r') as fp: | ||
data = fp.read().replace('\n', '') | ||
etree = lxmltree.HTML(data) | ||
etree_cache.clear() | ||
etree_cache[fname] = etree | ||
return etree | ||
|
||
yield parse | ||
etree_cache.clear() | ||
|
||
|
||
def flat_dict(d): | ||
return chain.from_iterable([zip(cycle([fname]), values) for fname, values in d.items()]) | ||
|
||
|
||
def check_xpath(etree, fname, path, check, be_found=True): | ||
nodes = list(etree.xpath(path)) | ||
if check is None: | ||
assert nodes == [], f'found any nodes matching xpath {path!r} in file {fname}' | ||
return | ||
else: | ||
assert nodes != [], f'did not find any node matching xpath {path!r} in file {fname}' | ||
if callable(check): | ||
check(nodes) | ||
elif not check: | ||
# only check for node presence | ||
pass | ||
else: | ||
|
||
def get_text(node): | ||
if node.text is not None: | ||
# the node has only one text | ||
return node.text | ||
else: | ||
# the node has tags and text; gather texts just under the node | ||
return ''.join(n.tail or '' for n in node) | ||
|
||
rex = re.compile(check) | ||
if be_found: | ||
if any(rex.search(get_text(node)) for node in nodes): | ||
return | ||
else: | ||
if all(not rex.search(get_text(node)) for node in nodes): | ||
return | ||
|
||
raise AssertionError(f'{check!r} not found in any node matching path {path} in {fname}: {[node.text for node in nodes]!r}') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
extensions = ["sphinxarg.ext"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Sample | ||
###### | ||
|
||
.. argparse:: | ||
:filename: test/sample-directive-opts.py | ||
:prog: sample-directive-opts | ||
:func: get_parser |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Command A | ||
========= | ||
|
||
.. argparse:: | ||
:filename: test/sample-directive-opts.py | ||
:prog: sample-directive-opts | ||
:func: get_parser | ||
:path: A |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import argparse | ||
|
||
|
||
def get_parser(): | ||
parser = argparse.ArgumentParser(prog='sample-directive-opts', description='Support SphinxArgParse HTML testing') | ||
subparsers = parser.add_subparsers() | ||
parser_a = subparsers.add_parser('A', help='A subparser') | ||
parser_a.add_argument('baz', type=int, help='An integer') | ||
parser_b = subparsers.add_parser('B', help='B subparser') | ||
parser_b.add_argument('--barg', choices='XYZ', help='A list of choices') | ||
|
||
parser.add_argument('--foo', help='foo help') | ||
parser.add_argument('foo2', metavar='foo2 metavar', help='foo2 help') | ||
grp1 = parser.add_argument_group('bar options') | ||
grp1.add_argument('--bar', help='bar help') | ||
grp1.add_argument('quux', help='quux help') | ||
grp2 = parser.add_argument_group('bla options') | ||
grp2.add_argument('--blah', help='blah help') | ||
grp2.add_argument('sniggly', help='sniggly help') | ||
|
||
return parser |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
"""Test the HTML builder and check output against XPath.""" | ||
|
||
import pytest | ||
|
||
from .conftest import check_xpath, flat_dict | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"fname,expect", | ||
flat_dict( | ||
{ | ||
'index.html': [ | ||
(".//h1", 'Sample'), | ||
(".//h1", 'blah-blah', False), | ||
(".//div[@class='highlight']//span", 'usage'), | ||
(".//h2", 'Positional Arguments'), | ||
(".//section[@id='positional-arguments']", ''), | ||
(".//section[@id='positional-arguments']/dl/dt[1]/kbd", 'foo2 metavar'), | ||
(".//section[@id='named-arguments']", ''), | ||
(".//section[@id='named-arguments']/dl/dt[1]/kbd", '--foo'), | ||
(".//section[@id='bar-options']", ''), | ||
(".//section[@id='bar-options']/dl/dt[1]/kbd", '--bar'), | ||
], | ||
'subcommand-a.html': [ | ||
(".//h1", 'Sample', False), | ||
(".//h1", 'Command A'), | ||
(".//div[@class='highlight']//span", 'usage'), | ||
(".//h2", 'Positional Arguments'), | ||
(".//section[@id='positional-arguments']", ''), | ||
(".//section[@id='positional-arguments']/dl/dt[1]/kbd", 'baz'), | ||
], | ||
} | ||
), | ||
) | ||
@pytest.mark.sphinx('html', testroot='default-html') | ||
def test_default_html(app, cached_etree_parse, fname, expect): | ||
app.build() | ||
print(app.outdir / fname) | ||
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) |