datasette/tests/test_docs.py
Simon Willison e8625695a3
xfail documentation unit tests for view classes, refs #299
More documentation unit tests. These ones check that every single **View class
imported into the datasette/app.py module are covered by our documentation.

Just one problem: they aren't documented yet. So I'm using the xfail pytest
decorator to mark these tests as allowed-to-fail. When you run the test suite
you now get a report of how many views still need to be documented, but it
doesn't fail the tests.

The output looks something like this:

    $ pytest tests/test_docs.py
    collected 31 items

    tests/test_docs.py ..........................XXXxx.                [100%]

    ============ 26 passed, 2 xfailed, 3 xpassed in 1.06 seconds ============

Once I have documented all the views I will remove the xfail so any future
views that are added without documentation will cause a test failure.

We can detect that a view is documented by looking for ReST label in the docs,
for example:

    .. _IndexView:

Some view classes can be used to power multiple URLs - the JsonDataView class
for example is used to power /-/metadata and /-/config and /-/plugins

In this case, the second part of the label can indicate the variety of page, e.g:

    .. _JsonDataView_metadata:

The test will pass as long as there is at least one label that starts with
_JsonDataView.
2018-07-27 20:13:26 -07:00

74 lines
2.3 KiB
Python

"""
Tests to ensure certain things are documented.
"""
from click.testing import CliRunner
from datasette import app
from datasette.cli import cli
from pathlib import Path
import pytest
import re
docs_path = Path(__file__).parent.parent / 'docs'
label_re = re.compile(r'\.\. _([^\s:]+):')
def get_headings(filename, underline="-"):
markdown = (docs_path / filename).open().read()
heading_re = re.compile(r'(\S+)\n\{}+\n'.format(underline))
return set(heading_re.findall(markdown))
def get_labels(filename):
markdown = (docs_path / filename).open().read()
return set(label_re.findall(markdown))
@pytest.mark.parametrize('config', app.CONFIG_OPTIONS)
def test_config_options_are_documented(config):
assert config.name in get_headings("config.rst")
@pytest.mark.parametrize("name,filename", (
("serve", "datasette-serve-help.txt"),
("package", "datasette-package-help.txt"),
("publish now", "datasette-publish-now-help.txt"),
("publish heroku", "datasette-publish-heroku-help.txt"),
))
def test_help_includes(name, filename):
expected = open(str(docs_path / filename)).read()
runner = CliRunner()
result = runner.invoke(cli, name.split() + ["--help"], terminal_width=88)
actual = "$ datasette {} --help\n\n{}".format(
name, result.output
)
# actual has "Usage: cli package [OPTIONS] FILES"
# because it doesn't know that cli will be aliased to datasette
expected = expected.replace("Usage: datasette", "Usage: cli")
assert expected == actual
@pytest.mark.parametrize('plugin', [
name for name in dir(app.pm.hook) if not name.startswith('_')
])
def test_plugin_hooks_are_documented(plugin):
headings = [
s.split("(")[0] for s in get_headings("plugins.rst", "~")
]
assert plugin in headings
@pytest.fixture
def documented_views():
view_labels = set()
for filename in docs_path.glob("*.rst"):
for label in get_labels(filename):
first_word = label.split("_")[0]
if first_word.endswith("View"):
view_labels.add(first_word)
return view_labels
@pytest.mark.xfail
@pytest.mark.parametrize("view_class", [v for v in dir(app) if v.endswith("View")])
def test_view_classes_are_documented(documented_views, view_class):
assert view_class in documented_views