Commit 46a2a1f2 authored by Sandro Tosi's avatar Sandro Tosi

New upstream version 0.2.1

parent a6d24663
Metadata-Version: 1.1
Metadata-Version: 2.1
Name: sphinxtesters
Version: 0.1.1
Version: 0.2.1
Summary: Utilities for testing Sphinx extensions
Home-page: http://github.com/matthew-brett/sphinxtesters
Author: Matthew Brett
Author-email: matthew.brett@gmail.com
Maintainer: Matthew Brett
Maintainer-email: matthew.brett@gmail.com
License: BSD license
Description: #######################################################
Sphinxtesters - utilities for testing Sphinx extensions
......@@ -16,6 +18,70 @@ Description: #######################################################
Quickstart
**********
If you have a directory containing a sphinx project, test that it builds with
something like:
.. code:: python
class TestMyProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
def test_basic_build(self):
# Get doctree for page "a_page.rst"
doctree = self.get_doctree('a_page')
# Assert stuff about doctree version of page
html = self.get_built_file('a_page.html')
# Assert stuff about html version of page
You can try adding other page content by using the ``rst_sources`` dictionary:
.. code:: python
class TestChangedProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
rst_sources = {'a_page': """Replacement text for page""",
'b_page': """An entirely new page"""}
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
Set the text of the ``conf.py`` file with the ``conf_source`` attribute:
.. code:: python
class TestConfeddProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
rst_sources = {'a_page': """Replacement text for page""",
'b_page': """An entirely new page"""}
conf_source = """ # This overwrites existing conf.py """
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
You don't need to set ``page_source_template``; if you don't, you start with a
fresh project, where the only pages are the ones you specify in
``rst_sources``.
.. code:: python
class TestFreshProject(SourcesBuilder):
rst_sources = {'a_page': """A new page""",
'b_page': """Another new page"""}
conf_source = """ # Stuff for the conf.py file """
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
See the tests for examples of using Sphinxtesters for testing builds of Sphinx
projects.
......@@ -46,13 +112,13 @@ Description: #######################################################
*****
* Install ``sphinxtesters``
* Install the nose_ testing framework::
* Install the pytest_ testing framework::
pip install nose
pip install pytest
* Run the tests with::
nosetests sphinxtesters
pytest sphinxtesters
*******
Support
......@@ -72,7 +138,7 @@ Description: #######################################################
.. _sphinx: http://sphinx-doc.org
.. _rest: http://docutils.sourceforge.net/rst.html
.. _sphinxtesters issue tracker: https://github.com/matthew-brett/sphinxtesters/issues
.. _nose: http://readthedocs.org/docs/nose/en/latest
.. _pytest: https://pytest.org
.. _mock: https://github.com/testing-cabal/mock
Platform: UNKNOWN
......@@ -88,3 +154,4 @@ Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Operating System :: Unix
Classifier: Operating System :: MacOS
Provides-Extra: test
......@@ -8,6 +8,70 @@ Sphinxtesters - utilities for testing Sphinx extensions
Quickstart
**********
If you have a directory containing a sphinx project, test that it builds with
something like:
.. code:: python
class TestMyProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
def test_basic_build(self):
# Get doctree for page "a_page.rst"
doctree = self.get_doctree('a_page')
# Assert stuff about doctree version of page
html = self.get_built_file('a_page.html')
# Assert stuff about html version of page
You can try adding other page content by using the ``rst_sources`` dictionary:
.. code:: python
class TestChangedProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
rst_sources = {'a_page': """Replacement text for page""",
'b_page': """An entirely new page"""}
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
Set the text of the ``conf.py`` file with the ``conf_source`` attribute:
.. code:: python
class TestConfeddProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
rst_sources = {'a_page': """Replacement text for page""",
'b_page': """An entirely new page"""}
conf_source = """ # This overwrites existing conf.py """
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
You don't need to set ``page_source_template``; if you don't, you start with a
fresh project, where the only pages are the ones you specify in
``rst_sources``.
.. code:: python
class TestFreshProject(SourcesBuilder):
rst_sources = {'a_page': """A new page""",
'b_page': """Another new page"""}
conf_source = """ # Stuff for the conf.py file """
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
See the tests for examples of using Sphinxtesters for testing builds of Sphinx
projects.
......@@ -38,13 +102,13 @@ Tests
*****
* Install ``sphinxtesters``
* Install the nose_ testing framework::
* Install the pytest_ testing framework::
pip install nose
pip install pytest
* Run the tests with::
nosetests sphinxtesters
pytest sphinxtesters
*******
Support
......@@ -64,5 +128,5 @@ Please put up issues on the `sphinxtesters issue tracker`_.
.. _sphinx: http://sphinx-doc.org
.. _rest: http://docutils.sourceforge.net/rst.html
.. _sphinxtesters issue tracker: https://github.com/matthew-brett/sphinxtesters/issues
.. _nose: http://readthedocs.org/docs/nose/en/latest
.. _pytest: https://pytest.org
.. _mock: https://github.com/testing-cabal/mock
......@@ -3,7 +3,7 @@ vcs = git
style = pep440
versionfile_source = sphinxtesters/_version.py
versionfile_build = sphinxtesters/_version.py
tag_prefix =
tag_prefix = ""
parentdir_prefix = sphinxtesters-
[egg_info]
......
Metadata-Version: 1.1
Metadata-Version: 2.1
Name: sphinxtesters
Version: 0.1.1
Version: 0.2.1
Summary: Utilities for testing Sphinx extensions
Home-page: http://github.com/matthew-brett/sphinxtesters
Author: Matthew Brett
Author-email: matthew.brett@gmail.com
Maintainer: Matthew Brett
Maintainer-email: matthew.brett@gmail.com
License: BSD license
Description: #######################################################
Sphinxtesters - utilities for testing Sphinx extensions
......@@ -16,6 +18,70 @@ Description: #######################################################
Quickstart
**********
If you have a directory containing a sphinx project, test that it builds with
something like:
.. code:: python
class TestMyProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
def test_basic_build(self):
# Get doctree for page "a_page.rst"
doctree = self.get_doctree('a_page')
# Assert stuff about doctree version of page
html = self.get_built_file('a_page.html')
# Assert stuff about html version of page
You can try adding other page content by using the ``rst_sources`` dictionary:
.. code:: python
class TestChangedProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
rst_sources = {'a_page': """Replacement text for page""",
'b_page': """An entirely new page"""}
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
Set the text of the ``conf.py`` file with the ``conf_source`` attribute:
.. code:: python
class TestConfeddProject(SourcesBuilder):
page_source_template = 'path/to/sphinx_dir'
rst_sources = {'a_page': """Replacement text for page""",
'b_page': """An entirely new page"""}
conf_source = """ # This overwrites existing conf.py """
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
You don't need to set ``page_source_template``; if you don't, you start with a
fresh project, where the only pages are the ones you specify in
``rst_sources``.
.. code:: python
class TestFreshProject(SourcesBuilder):
rst_sources = {'a_page': """A new page""",
'b_page': """Another new page"""}
conf_source = """ # Stuff for the conf.py file """
def test_basic_build(self):
a_doctree = self.get_doctree('a_page')
b_doctree = self.get_doctree('b_page')
# Your tests for the new page content here
See the tests for examples of using Sphinxtesters for testing builds of Sphinx
projects.
......@@ -46,13 +112,13 @@ Description: #######################################################
*****
* Install ``sphinxtesters``
* Install the nose_ testing framework::
* Install the pytest_ testing framework::
pip install nose
pip install pytest
* Run the tests with::
nosetests sphinxtesters
pytest sphinxtesters
*******
Support
......@@ -72,7 +138,7 @@ Description: #######################################################
.. _sphinx: http://sphinx-doc.org
.. _rest: http://docutils.sourceforge.net/rst.html
.. _sphinxtesters issue tracker: https://github.com/matthew-brett/sphinxtesters/issues
.. _nose: http://readthedocs.org/docs/nose/en/latest
.. _pytest: https://pytest.org
.. _mock: https://github.com/testing-cabal/mock
Platform: UNKNOWN
......@@ -88,3 +154,4 @@ Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Operating System :: Unix
Classifier: Operating System :: MacOS
Provides-Extra: test
""" Sphinxtesters package
"""
from .sphinxutils import (SourcesBuilder, ModifiedPageBuilder, TempApp)
from .sphinxutils import (PageBuilder, SourcesBuilder, ModifiedPageBuilder,
TempApp)
from ._version import get_versions
__version__ = get_versions()['version']
......
......@@ -8,11 +8,11 @@ import json
version_json = '''
{
"date": "2017-08-31T17:21:43+0100",
"date": "2018-08-22T12:02:53+0100",
"dirty": false,
"error": null,
"full-revisionid": "5c6a22fbc9da79a664c16d5157948e8d485bfaa1",
"version": "0.1.1"
"full-revisionid": "42e8318b246e6a2c4ff96b826e17ddf17a176b1d",
"version": "0.2.1"
}
''' # END VERSION_JSON
......
......@@ -3,12 +3,13 @@
import sys
import os
from os.path import join as pjoin, isdir, split as psplit
from os.path import join as pjoin, isdir, split as psplit, isfile
import shutil
from contextlib import contextmanager
from copy import copy
from tempfile import mkdtemp
import pickle
import warnings
from docutils import nodes
......@@ -22,6 +23,17 @@ fresh_directives = copy(directives._directives)
fresh_visitor_dict = nodes.GenericNodeVisitor.__dict__.copy()
fresh_std_domain_init_labels = StandardDomain.initial_data['labels'].copy()
@contextmanager
def in_dir(path):
""" Change into directory for duration of context
"""
cwd = os.getcwd()
try:
os.chdir(path)
yield
finally:
os.chdir(cwd)
def reset_class(cls, original_dict):
for key in list(cls.__dict__):
......@@ -102,15 +114,15 @@ class TempApp(TestApp):
self._set_cache()
with self.own_namespace():
TestApp.__init__(self,
tmp_dir,
tmp_dir,
tmp_dir,
tmp_dir,
buildername,
srcdir=tmp_dir,
confdir=tmp_dir,
# Sphinx 1.8.0b1 does not allow srcdir==outdir
outdir=pjoin(tmp_dir, 'build'),
doctreedir=tmp_dir,
buildername=buildername,
status=status,
warningiserror=warningiserror)
def cleanup(self):
if self.tmp_dir is None:
return
......@@ -127,10 +139,19 @@ class TempApp(TestApp):
class PageBuilder(object):
""" Nose test class to build sphinx pages in temporary directory
When child class has a name Nose recognizes as a test class, Nose will call
``setup_class``, which will build
""" Test class to build sphinx pages in temporary directory
When child class has a name Pytest recognizes as a test class, Pytest will
call :meth:`setup_class`. In this class method, :meth:`set_page_source`
copies / makes / manipulates the source pages. It likely calls
:meth:`modify_source` at the end, allowing you to hook in any other
modifications. :meth:`setup_class` then initializes the Sphinx applicaton
object, and builds the pages, using :meth:`build_source`.
The default behavior is to initialize the source directory by copying from
a template directory specified in ``page_source_template``. This can be
None, to start with an empty source directory, before modifications by
:meth:`modify_source`.
"""
# If True, assert that the build raised an error
......@@ -139,6 +160,10 @@ class PageBuilder(object):
# Builder
builder = 'html'
# Set to path containing any original sources that we copy to initialize
# the source directory. Can be None (no pages copied).
page_source_template = None
@classmethod
def setup_class(cls):
cls.build_error = None
......@@ -161,6 +186,22 @@ class PageBuilder(object):
raise e
cls.build_source()
@classmethod
def set_page_source(cls):
""" Set directory containing page sources, maybe modify source.
"""
cls.page_source = pjoin(cls.build_path, 'source')
if cls.page_source_template:
shutil.copytree(cls.page_source_template, cls.page_source)
else:
os.mkdir(cls.page_source)
cls.modify_source()
@classmethod
def modify_source(cls):
""" Override to modify sources before initial build
"""
@classmethod
def build_source(cls):
try: # Catch exceptions during sphinx build
......@@ -177,19 +218,6 @@ class PageBuilder(object):
raise RuntimeError('page build failed with build error {}'
.format(cls.build_error))
@classmethod
def set_page_source(cls):
""" Set directory containing page sources, maybe modify source
"""
cls.page_source = pjoin(cls.build_path, 'source')
os.mkdir(cls.page_source)
cls.modify_source()
@classmethod
def modify_source(cls):
""" Override to modify sources before initial build
"""
def get_doctree(self, name):
""" Return doctree given by `name` from pickle in doctree file """
with open(pjoin(self.doctree_dir, name + '.doctree'), 'rb') as fobj:
......@@ -226,6 +254,12 @@ class PageBuilder(object):
# Check whether an expected build error has occurred
assert self.should_error == (self.build_error is not None)
@classmethod
def append_conf(cls, string):
""" Append stuff to the conf.py file """
with open(pjoin(cls.page_source, 'conf.py'), 'a') as fobj:
fobj.write(string)
@classmethod
def teardown_class(cls):
if isdir(cls.build_path):
......@@ -234,6 +268,10 @@ class PageBuilder(object):
class SourcesBuilder(PageBuilder):
""" Build pages with text in class attribute ``rst_sources``.
Class that stores page names, page contents as key, value pairs in the
``rst_sources`` class attribute. ``conf.py`` contents can go in the
``conf_source`` class attribute.
"""
# rst_sources is a dict with key, value pairs, where the keys are the page
......@@ -241,12 +279,29 @@ class SourcesBuilder(PageBuilder):
# running on. ``.rst`` extension assumed (do not add it). The values are
# strings giving the page contents.
rst_sources = dict()
# Contents for conf.py. Can be empty to use existing contents. If not
# empty, then contents overwrites any existing conf.py file.
conf_source = ''
# Pages to be listed in the master document toctree
toctree_pages = []
@classmethod
def _touch(cls, fname):
if isfile(fname):
return
with open(fname, 'wt') as fobj:
fobj.write('')
@classmethod
def modify_source(cls):
with open(pjoin(cls.page_source, 'conf.py'), 'wt') as fobj:
fobj.write(cls.conf_source)
conf_fname = pjoin(cls.page_source, 'conf.py')
if cls.conf_source:
with open(conf_fname, 'wt') as fobj:
fobj.write(cls.conf_source)
else:
cls._touch(conf_fname)
for page_root, page_content in cls.rst_sources.items():
# page root may contain directories
page_root = page_root.replace('/', os.path.sep)
......@@ -256,44 +311,60 @@ class SourcesBuilder(PageBuilder):
os.makedirs(page_dir)
page_path = pjoin(page_dir, page_base + '.rst')
with open(page_path, 'wt') as fobj:
# Avoid warning (-> error) when page not included in toctree
fobj.write(":orphan:\n\n")
fobj.write(page_content)
# Add pages to toctree to avoid sphinx warning -> error
indent = ' ' * 4
page_list = ("\n" + indent).join(cls.rst_sources)
page_text = "\n\n.. toctree::\n{}:hidden:\n\n{}{}\n\n".format(
indent,
indent,
page_list)
with open(pjoin(cls.page_source, 'contents.rst'), 'wt') as fobj:
fobj.write(page_text)
master_fname = cls._get_master()
# Always write blank master document, if not already present.
cls._touch(master_fname)
# Write toctree to master doc, if needed
cls.write_toctree(cls.toctree_pages, master_fname)
@classmethod
def get_conf_vars(cls, force=False):
vars = {}
with in_dir(cls.page_source):
with open('conf.py', 'rt') as fobj:
conf = fobj.read()
exec(conf, {}, vars)
return vars
@classmethod
def _get_master(cls):
""" Return filename of master page for project """
master_doc = cls.get_conf_vars().get('master_doc', 'contents')
return pjoin(cls.page_source, master_doc + '.rst')
@classmethod
def write_toctree(cls, page_list, master_fname=None):
""" Write toctree directive for given page list
"""
if len(page_list) == 0:
return
if master_fname is None:
master_fname = cls._get_master()
with open(master_fname, 'at') as fobj:
fobj.write("\n\n.. toctree::\n\n {0}\n\n".format(
'\n'.join(page_list)))
class ModifiedPageBuilder(PageBuilder):
""" Copy existing pages into temporary directory and modify before build.
""" Add utilities for changing pages from template.
This allows us to make new build configurations and pages in the test
functions rather than having multiple sphinx project subdirectories in the
test tree.
This class now deprecated, please use PageBuilder instead.
"""
# set to path containing original sources (not modified)
page_source_template = None
# Default page. Should specify a path-less page name that can be replaced
# in modified builds.
default_page = None
@classmethod
def set_page_source(cls):
""" Copy the source to a temporary directory, maybe modify """
cls.page_source = pjoin(cls.build_path, 'source')
shutil.copytree(cls.page_source_template, cls.page_source)
cls.modify_source()
@classmethod
def append_conf(cls, string):
""" Append stuff to the conf.py file """
with open(pjoin(cls.page_source, 'conf.py'), 'a') as fobj:
fobj.write(string)
def setup_class(cls):
warnings.warn('ModifedPageBuilder deprecated, please '
'use PageBuilder instead',
DeprecationWarning,
stacklevel=2)
super(ModifiedPageBuilder, cls).setup_class()
@classmethod
def replace_page(cls, file_like):
......
......@@ -7,18 +7,11 @@ from os.path import dirname, join as pjoin
from sphinxtesters.sphinxutils import ModifiedPageBuilder
from sphinxtesters.tmpdirs import in_dtemp
from nose.tools import assert_equal, assert_raises
import pytest
HERE = dirname(__file__)
PROJ1 = pjoin(HERE, 'proj1')
NEW_PAGE = u"""
Fancy title
+++++++++++
Compelling text
"""
class TestModifiedPageBuilder(ModifiedPageBuilder):
# Replace page with file-like object
......@@ -43,7 +36,7 @@ Compelling text
expected = (
'<title>Fancy title</title>\n'
'<paragraph>Compelling text</paragraph>')
assert_equal(doctree_str, expected)
assert doctree_str == expected
class TestFModifiedPageBuilder(TestModifiedPageBuilder):
......@@ -70,7 +63,8 @@ Fancy title
:ref:`not-a-target`
"""
assert_raises(RuntimeError, TestBadPageBuilder.setup_class)
with pytest.raises(RuntimeError):
TestBadPageBuilder.setup_class()
class TestAppendConf(TestModifiedPageBuilder):
......@@ -86,7 +80,7 @@ class TestAppendConf(TestModifiedPageBuilder):
before_contents = fobj.read()
with open(pjoin(self.page_source, 'conf.py'), 'rt') as fobj:
after_contents = fobj.read()
assert_equal(after_contents, before_contents + '# Spurious comment')
assert (after_contents == before_contents + '# Spurious comment')
class TestAddPage(TestModifiedPageBuilder):
......@@ -105,13 +99,13 @@ class TestAddPage(TestModifiedPageBuilder):
'<paragraph>Some text.</paragraph>\n'
'<paragraph>More text.</paragraph>\n'
'<paragraph>Text is endless.</paragraph>')
assert_equal(doctree_str, expected)
assert doctree_str == expected
expected = (
'<title>Fancy title</title>\n'
'<paragraph>Compelling text</paragraph>')
doctree = self.get_doctree('b_page')
doctree_str = self.doctree2str(doctree)
assert_equal(doctree_str, expected)
assert doctree_str == expected
class TestAddFPage(TestAddPage):
......
......@@ -3,35 +3,71 @@
from os.path import (dirname, join as pjoin, isdir, isfile)
try: # Sphinx 1.8.0b1
from sphinx.errors import ApplicationError as NoConfigError
except ImportError:
from sphinx.errors import ConfigError as NoConfigError
from sphinxtesters.sphinxutils import PageBuilder
from nose.tools import assert_true, assert_equal, assert_raises
import pytest
HERE = dirname(__file__)
PROJ1 = pjoin(HERE, 'proj1')
class TestPageBuilder(PageBuilder):
# Test a minmal source tree
@classmethod
def set_page_source(cls):
cls.page_source = PROJ1
def modify_source(cls):
# Make an empty conf.py and contents.rst file with text
cls.append_conf('')
with open(pjoin(cls.page_source, 'contents.rst'), 'wt') as fobj:
fobj.write('some text')
def test_build(self):
assert isdir(self.out_dir)
assert isdir(self.doctree_dir)
assert isfile(pjoin(self.out_dir, 'contents.html'))
doctree = self.get_doctree('contents')
assert doctree.document.astext() == 'some text'
class TestMaster(PageBuilder):
# Test we can change the master document
@classmethod
def modify_source(cls):
cls.append_conf('master_doc = "foo"')
with open(pjoin(cls.page_source, 'foo.rst'), 'wt') as fobj:
fobj.write('more text')
def test_build(self):
assert not isfile(pjoin(self.out_dir, 'contents.html'))
assert isfile(pjoin(self.out_dir, 'foo.html'))
doctree = self.get_doctree('foo')
assert doctree.document.astext() == 'more text'
def test_basic_build(self):
assert_true(isdir(self.out_dir))
assert_true(isdir(self.doctree_dir))
class TestTemplatePageBuilder(PageBuilder):
page_source_template = PROJ1