From 561abdd16454b1e635325d10951363bde2f4ed83 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sat, 7 Jan 2017 00:46:26 +0900 Subject: [PATCH 1/6] pytest optimizatoin: - remote using deprecated decorators - remove gen_with_app - remove yield testing - remove pytest warnings - refactoring --- Makefile | 2 +- tests/conftest.py | 155 ++- tests/py35/test_autodoc_py35.py | 6 +- tests/run.py | 10 +- tests/test_autodoc.py | 43 +- tests/test_build.py | 63 +- tests/test_build_gettext.py | 46 +- tests/test_build_html.py | 1795 +++++++++++++++-------------- tests/test_config.py | 64 +- tests/test_correct_year.py | 54 +- tests/test_environment.py | 6 +- tests/test_environment_toctree.py | 55 +- tests/test_intl.py | 838 +++++++++----- tests/test_markup.py | 241 ++-- tests/test_util_i18n.py | 19 +- tests/test_util_nodes.py | 128 +- tests/test_versioning.py | 4 +- tests/test_websupport.py | 20 +- tests/util.py | 1 - 19 files changed, 1967 insertions(+), 1583 deletions(-) diff --git a/Makefile b/Makefile index d1a0fd457..b4a66d81d 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ PYTHON ?= python DONT_CHECK = -i build -i dist -i sphinx/style/jquery.js \ -i sphinx/pycode/pgen2 -i sphinx/util/smartypants.py \ -i .ropeproject -i doc/_build -i tests/path.py \ - -i tests/coverage.py -i utils/convert.py \ + -i utils/convert.py \ -i tests/typing_test_data.py \ -i tests/test_autodoc_py35.py \ -i tests/roots/test-warnings/undecodable.rst \ diff --git a/tests/conftest.py b/tests/conftest.py index c83c3f94b..8032e8e6c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,19 +1,25 @@ # -*- coding: utf-8 -*- +from __future__ import print_function + import sys import subprocess +from collections import namedtuple import pytest -from six import StringIO +from six import StringIO, string_types -from util import SphinxTestApp, path +import util @pytest.fixture -def app_params(request): +def app_params(request, test_params, shared_result): """ parameters that is specified by 'pytest.mark.sphinx' for sphinx.application.Sphinx initialization """ + + # ##### process pytest.mark.sphinx + markers = request.node.get_marker("sphinx") pargs = {} kwargs = {} @@ -26,17 +32,114 @@ def app_params(request): kwargs.update(info.kwargs) args = [pargs[i] for i in sorted(pargs.keys())] - return args, kwargs + + # ##### process pytest.mark.test_params + + if test_params['specific_srcdir'] and 'srcdir' not in kwargs: + kwargs['srcdir'] = test_params['specific_srcdir'] + + if test_params['shared_result']: + restore = shared_result.restore(test_params['shared_result']) + kwargs.update(restore) + + # ##### prepare Application params + + if 'srcdir' in kwargs: + srcdir = util.tempdir / kwargs['srcdir'] + else: + srcdir = util.tempdir / kwargs.get('testroot', 'root') + kwargs['srcdir'] = srcdir + + if kwargs.get('testroot') is None: + testroot_path = util.rootdir / 'root' + else: + testroot_path = util.rootdir / 'roots' / ('test-' + kwargs['testroot']) + + if not srcdir.exists(): + testroot_path.copytree(srcdir) + + return namedtuple('app_params', 'args,kwargs')(args, kwargs) + + +@pytest.fixture +def test_params(request): + """ + test parameters that is specified by 'pytest.mark.test_params' + + :param Union[str, bool, None] specific_srcdir: + If True, testroot directory will be copied into + '/'. + If string is specified, it copied into '/'. + You can used this feature for providing special crafted source + directory. Also you can used for sharing source directory for + parametrized testing and/or inter test functions. Default is None. + :param Union[str, bool, None] shared_result: + If True, app._status and app._warning objects will be shared in the + parametrized test functions. If string is specified, the objects will + be shred in the test functions that have same 'shared_result' value. + If you don't specify specific_srcdir, this option override + specific_srcdir param by 'shared_result' value. Default is None. + """ + env = request.node.get_marker('test_params') + kwargs = env.kwargs if env else {} + result = { + 'specific_srcdir': None, + 'shared_result': None, + } + result.update(kwargs) + + if (result['shared_result'] and + not isinstance(result['shared_result'], string_types)): + result['shared_result'] = request.node.originalname or request.node.name + + if result['shared_result'] and not result['specific_srcdir']: + result['specific_srcdir'] = result['shared_result'] + + if (result['specific_srcdir'] and + not isinstance(result['specific_srcdir'], string_types)): + result['specific_srcdir'] = request.node.originalname or request.node.name + + return result + + +class AppWrapper(object): + """ + This class is a wrapper for SphinxTestApp to speed up the test by skipping + `app.build` process if it is already built and there is even one output + file. + """ + + def __init__(self, app_): + self.app = app_ + + def __getattr__(self, name): + return getattr(self.app, name) + + def build(self, *args, **kw): + if not self.app.outdir.listdir(): + # if listdir is empty, do build. + self.app.build(*args, **kw) + # otherwise, we can use built cache @pytest.fixture(scope='function') -def app(app_params, make_app): +def app(test_params, app_params, make_app, shared_result): """ provides sphinx.application.Sphinx object """ args, kwargs = app_params app_ = make_app(*args, **kwargs) - return app_ + yield app_ + + print('# testroot:', kwargs.get('testroot', 'root')) + print('# builder:', app_.buildername) + print('# srcdir:', app_.srcdir) + print('# outdir:', app_.outdir) + print('# status:', '\n' + app_._status.getvalue()) + print('# warning:', '\n' + app_._warning.getvalue()) + + if test_params['shared_result']: + shared_result.store(test_params['shared_result'], app_) @pytest.fixture(scope='function') @@ -56,7 +159,7 @@ def warning(app): @pytest.fixture() -def make_app(): +def make_app(test_params): """ provides make_app function to initialize SphinxTestApp instance. if you want to initialize 'app' in your test function. please use this @@ -69,8 +172,10 @@ def make_app(): status, warning = StringIO(), StringIO() kwargs.setdefault('status', status) kwargs.setdefault('warning', warning) - app_ = SphinxTestApp(*args, **kwargs) + app_ = util.SphinxTestApp(*args, **kwargs) apps.append(app_) + if test_params['shared_result']: + app_ = AppWrapper(app_) return app_ yield make @@ -79,6 +184,38 @@ def make_app(): app_.cleanup() +class SharedResult(object): + cache = {} + + def store(self, key, app_): + if key in self.cache: + return + data = { + 'status': app_._status.getvalue(), + 'warning': app_._warning.getvalue(), + } + self.cache[key] = data + + def restore(self, key): + if key not in self.cache: + return {} + data = self.cache[key] + return { + 'status': StringIO(data['status']), + 'warning': StringIO(data['warning']), + } + + +@pytest.fixture +def shared_result(): + return SharedResult() + + +@pytest.fixture(scope='module', autouse=True) +def _shared_result_cache(): + SharedResult.cache.clear() + + @pytest.fixture def if_graphviz_found(app): """ @@ -105,4 +242,4 @@ def tempdir(tmpdir): temporary directory that wrapped with `path` class. this fixture is for compat with old test implementation. """ - return path(tmpdir) + return util.path(tmpdir) diff --git a/tests/py35/test_autodoc_py35.py b/tests/py35/test_autodoc_py35.py index 6049f7521..0a4fc872a 100644 --- a/tests/py35/test_autodoc_py35.py +++ b/tests/py35/test_autodoc_py35.py @@ -13,7 +13,7 @@ # "raises" imported for usage by autodoc import six import sys -from util import TestApp, Struct, raises, SkipTest +from util import SphinxTestApp, Struct import pytest from six import StringIO @@ -27,7 +27,7 @@ app = None def setup_module(): global app - app = TestApp() + app = SphinxTestApp() app.builder.env.app = app app.builder.env.temp_data['docname'] = 'dummy' app.connect('autodoc-process-docstring', process_docstring) @@ -185,7 +185,7 @@ def test_generate(): 'Class.meth', more_content=add_content) # test check_module - inst = FunctionDocumenter(directive, 'raises') + inst = FunctionDocumenter(directive, 'add_documenter') inst.generate(check_module=True) assert len(directive.result) == 0 diff --git a/tests/run.py b/tests/run.py index 72a668541..a7afbb8b6 100755 --- a/tests/run.py +++ b/tests/run.py @@ -17,11 +17,15 @@ import warnings import traceback from path import path -import pytest testroot = os.path.dirname(__file__) or '.' sys.path.insert(0, os.path.abspath(os.path.join(testroot, os.path.pardir))) +# filter warnings of test dependencies +warnings.filterwarnings('ignore', category=DeprecationWarning, module='site') # virtualenv +warnings.filterwarnings('ignore', category=ImportWarning, module='backports') +warnings.filterwarnings('ignore', category=PendingDeprecationWarning, module=r'_pytest\..*') + # check dependencies before testing print('Checking dependencies...') for modname in ('pytest', 'mock', 'six', 'docutils', 'jinja2', 'pygments', @@ -49,9 +53,6 @@ tempdir.makedirs() print('Running Sphinx test suite (with Python %s)...' % sys.version.split()[0]) sys.stdout.flush() -# filter warnings of test dependencies -warnings.filterwarnings('ignore', category=DeprecationWarning, module='site') # virtualenv - # exclude 'root' and 'roots' dirs for pytest test collector ignore_paths = [ os.path.relpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), sub)) @@ -61,4 +62,5 @@ args = sys.argv[1:] for path in ignore_paths: args.extend(['--ignore', path]) +import pytest sys.exit(pytest.main(args)) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 22eb02b34..06a3faf43 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -10,8 +10,7 @@ :license: BSD, see LICENSE for details. """ -# "raises" imported for usage by autodoc -from util import TestApp, Struct, raises, SkipTest # NOQA +from util import SphinxTestApp, Struct # NOQA import pytest import enum @@ -26,7 +25,7 @@ app = None def setup_module(): global app - app = TestApp() + app = SphinxTestApp() app.builder.env.app = app app.builder.env.temp_data['docname'] = 'dummy' app.connect('autodoc-process-docstring', process_docstring) @@ -125,26 +124,27 @@ def test_parse_name(): del _warnings[:] # for functions/classes - verify('function', 'util.raises', ('util', ['raises'], None, None)) - verify('function', 'util.raises(exc) -> None', - ('util', ['raises'], 'exc', 'None')) - directive.env.temp_data['autodoc:module'] = 'util' - verify('function', 'raises', ('util', ['raises'], None, None)) + verify('function', 'test_autodoc.raises', + ('test_autodoc', ['raises'], None, None)) + verify('function', 'test_autodoc.raises(exc) -> None', + ('test_autodoc', ['raises'], 'exc', 'None')) + directive.env.temp_data['autodoc:module'] = 'test_autodoc' + verify('function', 'raises', ('test_autodoc', ['raises'], None, None)) del directive.env.temp_data['autodoc:module'] - directive.env.ref_context['py:module'] = 'util' - verify('function', 'raises', ('util', ['raises'], None, None)) - verify('class', 'TestApp', ('util', ['TestApp'], None, None)) + directive.env.ref_context['py:module'] = 'test_autodoc' + verify('function', 'raises', ('test_autodoc', ['raises'], None, None)) + verify('class', 'Base', ('test_autodoc', ['Base'], None, None)) # for members directive.env.ref_context['py:module'] = 'foo' - verify('method', 'util.TestApp.cleanup', - ('util', ['TestApp', 'cleanup'], None, None)) + verify('method', 'util.SphinxTestApp.cleanup', + ('util', ['SphinxTestApp', 'cleanup'], None, None)) directive.env.ref_context['py:module'] = 'util' directive.env.ref_context['py:class'] = 'Foo' - directive.env.temp_data['autodoc:class'] = 'TestApp' - verify('method', 'cleanup', ('util', ['TestApp', 'cleanup'], None, None)) - verify('method', 'TestApp.cleanup', - ('util', ['TestApp', 'cleanup'], None, None)) + directive.env.temp_data['autodoc:class'] = 'SphinxTestApp' + verify('method', 'cleanup', ('util', ['SphinxTestApp', 'cleanup'], None, None)) + verify('method', 'SphinxTestApp.cleanup', + ('util', ['SphinxTestApp', 'cleanup'], None, None)) # and clean up del directive.env.ref_context['py:module'] @@ -658,7 +658,7 @@ def test_generate(): 'Class.meth', more_content=add_content) # test check_module - inst = FunctionDocumenter(directive, 'raises') + inst = FunctionDocumenter(directive, 'add_documenter') inst.generate(check_module=True) assert len(directive.result) == 0 @@ -878,6 +878,11 @@ __all__ = ['Class'] integer = 1 +def raises(exc, func, *args, **kwds): + """Raise AssertionError if ``func(*args, **kwds)`` does not raise *exc*.""" + pass + + class CustomEx(Exception): """My custom exception.""" @@ -1086,7 +1091,7 @@ def test_type_hints(): try: from typing_test_data import f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11 except (ImportError, SyntaxError): - raise SkipTest('Cannot import Python code with function annotations') + pytest.skip('Cannot import Python code with function annotations') def verify_arg_spec(f, expected): assert formatargspec(f, *getargspec(f)) == expected diff --git a/tests/test_build.py b/tests/test_build.py index d623d9b88..e067809d1 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -15,14 +15,8 @@ import mock import pytest from textwrap import dedent from sphinx.errors import SphinxError -import sphinx.builders.linkcheck -from util import rootdir, tempdir, SkipTest, TestApp, path - -try: - from docutils.writers.manpage import Writer as ManWriter -except ImportError: - ManWriter = None +from util import rootdir, tempdir, path def request_session_head(url, **kwargs): @@ -32,24 +26,17 @@ def request_session_head(url, **kwargs): return response -def verify_build(buildername, srcdir): - if buildername == 'man' and ManWriter is None: - raise SkipTest('man writer is not available') - app = TestApp(buildername=buildername, srcdir=srcdir) - try: - app.builder.build_all() - finally: - app.cleanup() - - -def test_build_all(): +@pytest.fixture +def nonascii_srcdir(request): # If supported, build in a non-ASCII source dir test_name = u'\u65e5\u672c\u8a9e' + basedir = tempdir / request.node.originalname try: - srcdir = tempdir / test_name - (rootdir / 'root').copytree(tempdir / test_name) + srcdir = basedir / test_name + if not srcdir.exists(): + (rootdir / 'root').copytree(srcdir) except UnicodeEncodeError: - srcdir = tempdir / 'all' + srcdir = basedir / 'all' else: # add a doc with a non-ASCII file name to the source dir (srcdir / (test_name + '.txt')).write_text(dedent(""" @@ -63,31 +50,33 @@ def test_build_all(): %(test_name)s/%(test_name)s """ % {'test_name': test_name}) - ) + ) + return srcdir - with mock.patch('sphinx.builders.linkcheck.requests') as requests: - requests.head = request_session_head +@pytest.mark.parametrize( + "buildername", + [ # note: no 'html' - if it's ok with dirhtml it's ok with html - for buildername in ['dirhtml', 'singlehtml', 'latex', 'texinfo', 'pickle', - 'json', 'text', 'htmlhelp', 'qthelp', 'epub2', 'epub', - 'applehelp', 'changes', 'xml', 'pseudoxml', 'man', - 'linkcheck']: - yield verify_build, buildername, srcdir + 'dirhtml', 'singlehtml', 'latex', 'texinfo', 'pickle', 'json', 'text', + 'htmlhelp', 'qthelp', 'epub2', 'epub', 'applehelp', 'changes', 'xml', + 'pseudoxml', 'man', 'linkcheck', + ], +) +@mock.patch('sphinx.builders.linkcheck.requests.head', + side_effect=request_session_head) +def test_build_all(requests_head, make_app, nonascii_srcdir, buildername): + app = make_app(buildername, srcdir=nonascii_srcdir) + app.build() -def test_master_doc_not_found(tempdir): +def test_master_doc_not_found(tempdir, make_app): (tempdir / 'conf.py').write_text('master_doc = "index"') assert tempdir.listdir() == ['conf.py'] - try: - app = TestApp(buildername='dummy', srcdir=tempdir) + app = make_app('dummy', srcdir=tempdir) + with pytest.raises(SphinxError): app.builder.build_all() - assert False # SphinxError not raised - except Exception as exc: - assert isinstance(exc, SphinxError) - finally: - app.cleanup() @pytest.mark.sphinx(buildername='text', testroot='circular') diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py index cbc17ab80..6354360d7 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -17,36 +17,36 @@ from subprocess import Popen, PIPE import pytest -from util import ( - gen_with_app, SkipTest, assert_in, assert_true, assert_equal -) +from sphinx.util.osutil import cd -@gen_with_app('gettext', srcdir='root-gettext') -def test_all(app, status, warning): +@pytest.mark.sphinx('gettext', srcdir='root-gettext') +def test_build_gettext(app): # Generic build; should fail only when the builder is horribly broken. app.builder.build_all() # Do messages end up in the correct location? # top-level documents end up in a message catalog - yield assert_true, (app.outdir / 'extapi.pot').isfile() + assert (app.outdir / 'extapi.pot').isfile() # directory items are grouped into sections - yield assert_true, (app.outdir / 'subdir.pot').isfile() + assert (app.outdir / 'subdir.pot').isfile() # regression test for issue #960 catalog = (app.outdir / 'markup.pot').text(encoding='utf-8') - yield assert_in, 'msgid "something, something else, something more"', catalog + assert 'msgid "something, something else, something more"' in catalog + +@pytest.mark.sphinx('gettext', srcdir='root-gettext') +def test_msgfmt(app): + app.builder.build_all() (app.outdir / 'en' / 'LC_MESSAGES').makedirs() - cwd = os.getcwd() - os.chdir(app.outdir) - try: + with cd(app.outdir): try: p = Popen(['msginit', '--no-translator', '-i', 'markup.pot', '--locale', 'en_US'], stdout=PIPE, stderr=PIPE) except OSError: - raise SkipTest # most likely msginit was not found + pytest.skip() # most likely msginit was not found else: stdout, stderr = p.communicate() if p.returncode != 0: @@ -54,13 +54,13 @@ def test_all(app, status, warning): print(stderr) assert False, 'msginit exited with return code %s' % \ p.returncode - yield assert_true, (app.outdir / 'en_US.po').isfile(), 'msginit failed' + assert (app.outdir / 'en_US.po').isfile(), 'msginit failed' try: p = Popen(['msgfmt', 'en_US.po', '-o', os.path.join('en', 'LC_MESSAGES', 'test_root.mo')], stdout=PIPE, stderr=PIPE) except OSError: - raise SkipTest # most likely msgfmt was not found + pytest.skip() # most likely msgfmt was not found else: stdout, stderr = p.communicate() if p.returncode != 0: @@ -68,20 +68,17 @@ def test_all(app, status, warning): print(stderr) assert False, 'msgfmt exited with return code %s' % \ p.returncode - yield (assert_true, - (app.outdir / 'en' / 'LC_MESSAGES' / 'test_root.mo').isfile(), - 'msgfmt failed') - finally: - os.chdir(cwd) + mo = app.outdir / 'en' / 'LC_MESSAGES' / 'test_root.mo' + assert mo.isfile(), 'msgfmt failed' _ = gettext.translation('test_root', app.outdir, languages=['en']).gettext - yield assert_equal, _("Testing various markup"), u"Testing various markup" + assert _("Testing various markup") == u"Testing various markup" @pytest.mark.sphinx( 'gettext', testroot='intl', srcdir='gettext', confoverrides={'gettext_compact': False}) -def test_gettext_index_entries(app, status, warning): +def test_gettext_index_entries(app): # regression test for #976 app.builder.build(['index_entries']) @@ -128,8 +125,9 @@ def test_gettext_index_entries(app, status, warning): @pytest.mark.sphinx( 'gettext', testroot='intl', srcdir='gettext', - confoverrides={'gettext_compact': False, 'gettext_additional_targets': []}) -def test_gettext_disable_index_entries(app, status, warning): + confoverrides={'gettext_compact': False, + 'gettext_additional_targets': []}) +def test_gettext_disable_index_entries(app): # regression test for #976 app.builder.build(['index_entries']) @@ -160,7 +158,7 @@ def test_gettext_disable_index_entries(app, status, warning): @pytest.mark.sphinx('gettext', testroot='intl', srcdir='gettext') -def test_gettext_template(app, status, warning): +def test_gettext_template(app): app.builder.build_all() assert (app.outdir / 'sphinx.pot').isfile() diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 3b8da34f0..4a4eac2b6 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -11,11 +11,12 @@ import os import re +from itertools import cycle, chain -from six import PY3, iteritems +from six import PY3 from sphinx import __display_version__ -from util import remove_unicode_literals, gen_with_app, strip_escseq +from util import remove_unicode_literals, strip_escseq from etree13 import ElementTree from html5lib import getTreeBuilder, HTMLParser import pytest @@ -49,6 +50,31 @@ if PY3: HTML_WARNINGS = remove_unicode_literals(HTML_WARNINGS) +etree_cache = {} + +@pytest.fixture(scope='module') +def cached_etree_parse(): + def parse(fname): + if fname in etree_cache: + return etree_cache[fname] + with (fname).open('rb') as fp: + etree = HTML_PARSER.parse(fp) + 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 tail_check(check): rex = re.compile(check) @@ -60,274 +86,6 @@ def tail_check(check): return checker -HTML_XPATH = { - 'images.html': [ - (".//img[@src='_images/img.png']", ''), - (".//img[@src='_images/img1.png']", ''), - (".//img[@src='_images/simg.png']", ''), - (".//img[@src='_images/svgimg.svg']", ''), - (".//a[@href='_sources/images.txt']", ''), - ], - 'subdir/images.html': [ - (".//img[@src='../_images/img1.png']", ''), - (".//img[@src='../_images/rimg.png']", ''), - ], - 'subdir/includes.html': [ - (".//a[@href='../_downloads/img.png']", ''), - (".//img[@src='../_images/img.png']", ''), - (".//p", 'This is an include file.'), - (".//pre/span", 'line 1'), - (".//pre/span", 'line 2'), - ], - 'includes.html': [ - (".//pre", u'Max Strauß'), - (".//a[@href='_downloads/img.png']", ''), - (".//a[@href='_downloads/img1.png']", ''), - (".//pre/span", u'"quotes"'), - (".//pre/span", u"'included'"), - (".//pre/span[@class='s2']", u'üöä'), - (".//div[@class='inc-pyobj1 highlight-text']//pre", - r'^class Foo:\n pass\n\s*$'), - (".//div[@class='inc-pyobj2 highlight-text']//pre", - r'^ def baz\(\):\n pass\n\s*$'), - (".//div[@class='inc-lines highlight-text']//pre", - r'^class Foo:\n pass\nclass Bar:\n$'), - (".//div[@class='inc-startend highlight-text']//pre", - u'^foo = "Including Unicode characters: üöä"\\n$'), - (".//div[@class='inc-preappend highlight-text']//pre", - r'(?m)^START CODE$'), - (".//div[@class='inc-pyobj-dedent highlight-python']//span", - r'def'), - (".//div[@class='inc-tab3 highlight-text']//pre", - r'-| |-'), - (".//div[@class='inc-tab8 highlight-python']//pre/span", - r'-| |-'), - ], - 'autodoc.html': [ - (".//dt[@id='test_autodoc.Class']", ''), - (".//dt[@id='test_autodoc.function']/em", r'\*\*kwds'), - (".//dd/p", r'Return spam\.'), - ], - 'extapi.html': [ - (".//strong", 'from function: Foo'), - (".//strong", 'from class: Bar'), - ], - 'markup.html': [ - (".//title", 'set by title directive'), - (".//p/em", 'Section author: Georg Brandl'), - (".//p/em", 'Module author: Georg Brandl'), - # created by the meta directive - (".//meta[@name='author'][@content='Me']", ''), - (".//meta[@name='keywords'][@content='docs, sphinx']", ''), - # a label created by ``.. _label:`` - (".//div[@id='label']", ''), - # code with standard code blocks - (".//pre", '^some code$'), - # an option list - (".//span[@class='option']", '--help'), - # admonitions - (".//p[@class='first admonition-title']", 'My Admonition'), - (".//p[@class='last']", 'Note text.'), - (".//p[@class='last']", 'Warning text.'), - # inline markup - (".//li/strong", r'^command\\n$'), - (".//li/strong", r'^program\\n$'), - (".//li/em", r'^dfn\\n$'), - (".//li/code/span[@class='pre']", r'^kbd\\n$'), - (".//li/span", u'File \N{TRIANGULAR BULLET} Close'), - (".//li/code/span[@class='pre']", '^a/$'), - (".//li/code/em/span[@class='pre']", '^varpart$'), - (".//li/code/em/span[@class='pre']", '^i$'), - (".//a[@href='https://www.python.org/dev/peps/pep-0008']" - "[@class='pep reference external']/strong", 'PEP 8'), - (".//a[@href='https://www.python.org/dev/peps/pep-0008']" - "[@class='pep reference external']/strong", - 'Python Enhancement Proposal #8'), - (".//a[@href='https://tools.ietf.org/html/rfc1.html']" - "[@class='rfc reference external']/strong", 'RFC 1'), - (".//a[@href='https://tools.ietf.org/html/rfc1.html']" - "[@class='rfc reference external']/strong", 'Request for Comments #1'), - (".//a[@href='objects.html#envvar-HOME']" - "[@class='reference internal']/code/span[@class='pre']", 'HOME'), - (".//a[@href='#with']" - "[@class='reference internal']/code/span[@class='pre']", '^with$'), - (".//a[@href='#grammar-token-try_stmt']" - "[@class='reference internal']/code/span", '^statement$'), - (".//a[@href='#some-label'][@class='reference internal']/span", '^here$'), - (".//a[@href='#some-label'][@class='reference internal']/span", '^there$'), - (".//a[@href='subdir/includes.html']" - "[@class='reference internal']/span", 'Including in subdir'), - (".//a[@href='objects.html#cmdoption-python-c']" - "[@class='reference internal']/code/span[@class='pre']", '-c'), - # abbreviations - (".//abbr[@title='abbreviation']", '^abbr$'), - # version stuff - (".//div[@class='versionadded']/p/span", 'New in version 0.6: '), - (".//div[@class='versionadded']/p/span", - tail_check('First paragraph of versionadded')), - (".//div[@class='versionchanged']/p/span", - tail_check('First paragraph of versionchanged')), - (".//div[@class='versionchanged']/p", - 'Second paragraph of versionchanged'), - # footnote reference - (".//a[@class='footnote-reference']", r'\[1\]'), - # created by reference lookup - (".//a[@href='contents.html#ref1']", ''), - # ``seealso`` directive - (".//div/p[@class='first admonition-title']", 'See also'), - # a ``hlist`` directive - (".//table[@class='hlist']/tbody/tr/td/ul/li", '^This$'), - # a ``centered`` directive - (".//p[@class='centered']/strong", 'LICENSE'), - # a glossary - (".//dl/dt[@id='term-boson']", 'boson'), - # a production list - (".//pre/strong", 'try_stmt'), - (".//pre/a[@href='#grammar-token-try1_stmt']/code/span", 'try1_stmt'), - # tests for ``only`` directive - (".//p", 'A global substitution.'), - (".//p", 'In HTML.'), - (".//p", 'In both.'), - (".//p", 'Always present'), - # tests for ``any`` role - (".//a[@href='#with']/span", 'headings'), - (".//a[@href='objects.html#func_without_body']/code/span", 'objects'), - ], - 'objects.html': [ - (".//dt[@id='mod.Cls.meth1']", ''), - (".//dt[@id='errmod.Error']", ''), - (".//dt/code", r'long\(parameter,\s* list\)'), - (".//dt/code", 'another one'), - (".//a[@href='#mod.Cls'][@class='reference internal']", ''), - (".//dl[@class='userdesc']", ''), - (".//dt[@id='userdesc-myobj']", ''), - (".//a[@href='#userdesc-myobj'][@class='reference internal']", ''), - # docfields - (".//a[@class='reference internal'][@href='#TimeInt']/em", 'TimeInt'), - (".//a[@class='reference internal'][@href='#Time']", 'Time'), - (".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'), - # C references - (".//span[@class='pre']", 'CFunction()'), - (".//a[@href='#c.Sphinx_DoSomething']", ''), - (".//a[@href='#c.SphinxStruct.member']", ''), - (".//a[@href='#c.SPHINX_USE_PYTHON']", ''), - (".//a[@href='#c.SphinxType']", ''), - (".//a[@href='#c.sphinx_global']", ''), - # test global TOC created by toctree() - (".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='#']", - 'Testing object descriptions'), - (".//li[@class='toctree-l1']/a[@href='markup.html']", - 'Testing various markup'), - # test unknown field names - (".//th[@class='field-name']", 'Field_name:'), - (".//th[@class='field-name']", 'Field_name all lower:'), - (".//th[@class='field-name']", 'FIELD_NAME:'), - (".//th[@class='field-name']", 'FIELD_NAME ALL CAPS:'), - (".//th[@class='field-name']", 'Field_Name:'), - (".//th[@class='field-name']", 'Field_Name All Word Caps:'), - (".//th[@class='field-name']", 'Field_name:'), - (".//th[@class='field-name']", 'Field_name First word cap:'), - (".//th[@class='field-name']", 'FIELd_name:'), - (".//th[@class='field-name']", 'FIELd_name PARTial caps:'), - # custom sidebar - (".//h4", 'Custom sidebar'), - # docfields - (".//td[@class='field-body']/strong", '^moo$'), - (".//td[@class='field-body']/strong", tail_check(r'\(Moo\) .* Moo')), - (".//td[@class='field-body']/ul/li/strong", '^hour$'), - (".//td[@class='field-body']/ul/li/em", '^DuplicateType$'), - (".//td[@class='field-body']/ul/li/em", tail_check(r'.* Some parameter')), - # others - (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", - 'perl'), - (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", - '\+p'), - (".//a[@class='reference internal'][@href='#cmdoption-perl-plugin-option']/code/span", - '--plugin.option'), - (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']" - "/code/span", - 'create-auth-token'), - (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-arg']/code/span", - 'arg'), - (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", - 'hg'), - (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", - 'commit'), - (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", - 'git'), - (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", - 'commit'), - (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", - '-p'), - ], - 'contents.html': [ - (".//meta[@name='hc'][@content='hcval']", ''), - (".//meta[@name='hc_co'][@content='hcval_co']", ''), - (".//meta[@name='testopt'][@content='testoverride']", ''), - (".//td[@class='label']", r'\[Ref1\]'), - (".//td[@class='label']", ''), - (".//li[@class='toctree-l1']/a", 'Testing various markup'), - (".//li[@class='toctree-l2']/a", 'Inline markup'), - (".//title", 'Sphinx '), - (".//div[@class='footer']", 'Georg Brandl & Team'), - (".//a[@href='http://python.org/']" - "[@class='reference external']", ''), - (".//li/a[@href='genindex.html']/span", 'Index'), - (".//li/a[@href='py-modindex.html']/span", 'Module Index'), - (".//li/a[@href='search.html']/span", 'Search Page'), - # custom sidebar only for contents - (".//h4", 'Contents sidebar'), - # custom JavaScript - (".//script[@src='file://moo.js']", ''), - # URL in contents - (".//a[@class='reference external'][@href='http://sphinx-doc.org/']", - 'http://sphinx-doc.org/'), - (".//a[@class='reference external'][@href='http://sphinx-doc.org/latest/']", - 'Latest reference'), - # Indirect hyperlink targets across files - (".//a[@href='markup.html#some-label'][@class='reference internal']/span", - '^indirect hyperref$'), - ], - 'bom.html': [ - (".//title", " File with UTF-8 BOM"), - ], - 'extensions.html': [ - (".//a[@href='http://python.org/dev/']", "http://python.org/dev/"), - (".//a[@href='http://bugs.python.org/issue1000']", "issue 1000"), - (".//a[@href='http://bugs.python.org/issue1042']", "explicit caption"), - ], - '_static/statictmpl.html': [ - (".//project", 'Sphinx '), - ], - 'genindex.html': [ - # index entries - (".//a/strong", "Main"), - (".//a/strong", "[1]"), - (".//a/strong", "Other"), - (".//a", "entry"), - (".//li/a", "double"), - ], - 'footnote.html': [ - (".//a[@class='footnote-reference'][@href='#id7'][@id='id1']", r"\[1\]"), - (".//a[@class='footnote-reference'][@href='#id8'][@id='id2']", r"\[2\]"), - (".//a[@class='footnote-reference'][@href='#foo'][@id='id3']", r"\[3\]"), - (".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"), - (".//a[@class='footnote-reference'][@href='#id9'][@id='id5']", r"\[4\]"), - (".//a[@class='footnote-reference'][@href='#id10'][@id='id6']", r"\[5\]"), - (".//a[@class='fn-backref'][@href='#id1']", r"\[1\]"), - (".//a[@class='fn-backref'][@href='#id2']", r"\[2\]"), - (".//a[@class='fn-backref'][@href='#id3']", r"\[3\]"), - (".//a[@class='fn-backref'][@href='#id4']", r"\[bar\]"), - (".//a[@class='fn-backref'][@href='#id5']", r"\[4\]"), - (".//a[@class='fn-backref'][@href='#id6']", r"\[5\]"), - ], - 'otherext.html': [ - (".//h1", "Generated section"), - (".//a[@href='_sources/otherext.foo.txt']", ''), - ] -} - - def check_xpath(etree, fname, path, check, be_found=True): nodes = list(etree.findall(path)) if check is None: @@ -385,9 +143,9 @@ def check_extra_entries(outdir): assert (outdir / 'robots.txt').isfile() -@pytest.mark.sphinx('html', testroot='warnings', freshenv=True) -def test_html_warnings(app, status, warning): - app.builder.build_all() +@pytest.mark.sphinx('html', testroot='warnings') +def test_html_warnings(app, warning): + app.build() html_warnings = strip_escseq(warning.getvalue().replace(os.sep, '/')) html_warnings_exp = HTML_WARNINGS % { 'root': re.escape(app.srcdir.replace(os.sep, '/'))} @@ -397,168 +155,425 @@ def test_html_warnings(app, status, warning): '--- Got:\n' + html_warnings -@gen_with_app(buildername='html', tags=['testtag'], - confoverrides={'html_context.hckey_co': 'hcval_co'}) -def test_html_output(app, status, warning): - app.builder.build_all() - for fname, paths in iteritems(HTML_XPATH): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - for path, check in paths: - yield check_xpath, etree, fname, path, check - +@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={ + 'html_context.hckey_co': 'hcval_co'}) +@pytest.mark.test_params(shared_result='test_build_html_output') +def test_static_output(app): + app.build() check_static_entries(app.builder.outdir) check_extra_entries(app.builder.outdir) -@gen_with_app(buildername='html', testroot='tocdepth') -def test_tocdepth(app, status, warning): +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'images.html': [ + (".//img[@src='_images/img.png']", ''), + (".//img[@src='_images/img1.png']", ''), + (".//img[@src='_images/simg.png']", ''), + (".//img[@src='_images/svgimg.svg']", ''), + (".//a[@href='_sources/images.txt']", ''), + ], + 'subdir/images.html': [ + (".//img[@src='../_images/img1.png']", ''), + (".//img[@src='../_images/rimg.png']", ''), + ], + 'subdir/includes.html': [ + (".//a[@href='../_downloads/img.png']", ''), + (".//img[@src='../_images/img.png']", ''), + (".//p", 'This is an include file.'), + (".//pre/span", 'line 1'), + (".//pre/span", 'line 2'), + ], + 'includes.html': [ + (".//pre", u'Max Strauß'), + (".//a[@href='_downloads/img.png']", ''), + (".//a[@href='_downloads/img1.png']", ''), + (".//pre/span", u'"quotes"'), + (".//pre/span", u"'included'"), + (".//pre/span[@class='s2']", u'üöä'), + (".//div[@class='inc-pyobj1 highlight-text']//pre", + r'^class Foo:\n pass\n\s*$'), + (".//div[@class='inc-pyobj2 highlight-text']//pre", + r'^ def baz\(\):\n pass\n\s*$'), + (".//div[@class='inc-lines highlight-text']//pre", + r'^class Foo:\n pass\nclass Bar:\n$'), + (".//div[@class='inc-startend highlight-text']//pre", + u'^foo = "Including Unicode characters: üöä"\\n$'), + (".//div[@class='inc-preappend highlight-text']//pre", + r'(?m)^START CODE$'), + (".//div[@class='inc-pyobj-dedent highlight-python']//span", + r'def'), + (".//div[@class='inc-tab3 highlight-text']//pre", + r'-| |-'), + (".//div[@class='inc-tab8 highlight-python']//pre/span", + r'-| |-'), + ], + 'autodoc.html': [ + (".//dt[@id='test_autodoc.Class']", ''), + (".//dt[@id='test_autodoc.function']/em", r'\*\*kwds'), + (".//dd/p", r'Return spam\.'), + ], + 'extapi.html': [ + (".//strong", 'from function: Foo'), + (".//strong", 'from class: Bar'), + ], + 'markup.html': [ + (".//title", 'set by title directive'), + (".//p/em", 'Section author: Georg Brandl'), + (".//p/em", 'Module author: Georg Brandl'), + # created by the meta directive + (".//meta[@name='author'][@content='Me']", ''), + (".//meta[@name='keywords'][@content='docs, sphinx']", ''), + # a label created by ``.. _label:`` + (".//div[@id='label']", ''), + # code with standard code blocks + (".//pre", '^some code$'), + # an option list + (".//span[@class='option']", '--help'), + # admonitions + (".//p[@class='first admonition-title']", 'My Admonition'), + (".//p[@class='last']", 'Note text.'), + (".//p[@class='last']", 'Warning text.'), + # inline markup + (".//li/strong", r'^command\\n$'), + (".//li/strong", r'^program\\n$'), + (".//li/em", r'^dfn\\n$'), + (".//li/code/span[@class='pre']", r'^kbd\\n$'), + (".//li/span", u'File \N{TRIANGULAR BULLET} Close'), + (".//li/code/span[@class='pre']", '^a/$'), + (".//li/code/em/span[@class='pre']", '^varpart$'), + (".//li/code/em/span[@class='pre']", '^i$'), + (".//a[@href='https://www.python.org/dev/peps/pep-0008']" + "[@class='pep reference external']/strong", 'PEP 8'), + (".//a[@href='https://www.python.org/dev/peps/pep-0008']" + "[@class='pep reference external']/strong", + 'Python Enhancement Proposal #8'), + (".//a[@href='https://tools.ietf.org/html/rfc1.html']" + "[@class='rfc reference external']/strong", 'RFC 1'), + (".//a[@href='https://tools.ietf.org/html/rfc1.html']" + "[@class='rfc reference external']/strong", 'Request for Comments #1'), + (".//a[@href='objects.html#envvar-HOME']" + "[@class='reference internal']/code/span[@class='pre']", 'HOME'), + (".//a[@href='#with']" + "[@class='reference internal']/code/span[@class='pre']", '^with$'), + (".//a[@href='#grammar-token-try_stmt']" + "[@class='reference internal']/code/span", '^statement$'), + (".//a[@href='#some-label'][@class='reference internal']/span", '^here$'), + (".//a[@href='#some-label'][@class='reference internal']/span", '^there$'), + (".//a[@href='subdir/includes.html']" + "[@class='reference internal']/span", 'Including in subdir'), + (".//a[@href='objects.html#cmdoption-python-c']" + "[@class='reference internal']/code/span[@class='pre']", '-c'), + # abbreviations + (".//abbr[@title='abbreviation']", '^abbr$'), + # version stuff + (".//div[@class='versionadded']/p/span", 'New in version 0.6: '), + (".//div[@class='versionadded']/p/span", + tail_check('First paragraph of versionadded')), + (".//div[@class='versionchanged']/p/span", + tail_check('First paragraph of versionchanged')), + (".//div[@class='versionchanged']/p", + 'Second paragraph of versionchanged'), + # footnote reference + (".//a[@class='footnote-reference']", r'\[1\]'), + # created by reference lookup + (".//a[@href='contents.html#ref1']", ''), + # ``seealso`` directive + (".//div/p[@class='first admonition-title']", 'See also'), + # a ``hlist`` directive + (".//table[@class='hlist']/tbody/tr/td/ul/li", '^This$'), + # a ``centered`` directive + (".//p[@class='centered']/strong", 'LICENSE'), + # a glossary + (".//dl/dt[@id='term-boson']", 'boson'), + # a production list + (".//pre/strong", 'try_stmt'), + (".//pre/a[@href='#grammar-token-try1_stmt']/code/span", 'try1_stmt'), + # tests for ``only`` directive + (".//p", 'A global substitution.'), + (".//p", 'In HTML.'), + (".//p", 'In both.'), + (".//p", 'Always present'), + # tests for ``any`` role + (".//a[@href='#with']/span", 'headings'), + (".//a[@href='objects.html#func_without_body']/code/span", 'objects'), + ], + 'objects.html': [ + (".//dt[@id='mod.Cls.meth1']", ''), + (".//dt[@id='errmod.Error']", ''), + (".//dt/code", r'long\(parameter,\s* list\)'), + (".//dt/code", 'another one'), + (".//a[@href='#mod.Cls'][@class='reference internal']", ''), + (".//dl[@class='userdesc']", ''), + (".//dt[@id='userdesc-myobj']", ''), + (".//a[@href='#userdesc-myobj'][@class='reference internal']", ''), + # docfields + (".//a[@class='reference internal'][@href='#TimeInt']/em", 'TimeInt'), + (".//a[@class='reference internal'][@href='#Time']", 'Time'), + (".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'), + # C references + (".//span[@class='pre']", 'CFunction()'), + (".//a[@href='#c.Sphinx_DoSomething']", ''), + (".//a[@href='#c.SphinxStruct.member']", ''), + (".//a[@href='#c.SPHINX_USE_PYTHON']", ''), + (".//a[@href='#c.SphinxType']", ''), + (".//a[@href='#c.sphinx_global']", ''), + # test global TOC created by toctree() + (".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='#']", + 'Testing object descriptions'), + (".//li[@class='toctree-l1']/a[@href='markup.html']", + 'Testing various markup'), + # test unknown field names + (".//th[@class='field-name']", 'Field_name:'), + (".//th[@class='field-name']", 'Field_name all lower:'), + (".//th[@class='field-name']", 'FIELD_NAME:'), + (".//th[@class='field-name']", 'FIELD_NAME ALL CAPS:'), + (".//th[@class='field-name']", 'Field_Name:'), + (".//th[@class='field-name']", 'Field_Name All Word Caps:'), + (".//th[@class='field-name']", 'Field_name:'), + (".//th[@class='field-name']", 'Field_name First word cap:'), + (".//th[@class='field-name']", 'FIELd_name:'), + (".//th[@class='field-name']", 'FIELd_name PARTial caps:'), + # custom sidebar + (".//h4", 'Custom sidebar'), + # docfields + (".//td[@class='field-body']/strong", '^moo$'), + (".//td[@class='field-body']/strong", tail_check(r'\(Moo\) .* Moo')), + (".//td[@class='field-body']/ul/li/strong", '^hour$'), + (".//td[@class='field-body']/ul/li/em", '^DuplicateType$'), + (".//td[@class='field-body']/ul/li/em", tail_check(r'.* Some parameter')), + # others + (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", + 'perl'), + (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", + '\+p'), + (".//a[@class='reference internal'][@href='#cmdoption-perl-plugin-option']/code/span", + '--plugin.option'), + (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']" + "/code/span", + 'create-auth-token'), + (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-arg']/code/span", + 'arg'), + (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", + 'hg'), + (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", + 'commit'), + (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", + 'git'), + (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", + 'commit'), + (".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span", + '-p'), + ], + 'contents.html': [ + (".//meta[@name='hc'][@content='hcval']", ''), + (".//meta[@name='hc_co'][@content='hcval_co']", ''), + (".//meta[@name='testopt'][@content='testoverride']", ''), + (".//td[@class='label']", r'\[Ref1\]'), + (".//td[@class='label']", ''), + (".//li[@class='toctree-l1']/a", 'Testing various markup'), + (".//li[@class='toctree-l2']/a", 'Inline markup'), + (".//title", 'Sphinx '), + (".//div[@class='footer']", 'Georg Brandl & Team'), + (".//a[@href='http://python.org/']" + "[@class='reference external']", ''), + (".//li/a[@href='genindex.html']/span", 'Index'), + (".//li/a[@href='py-modindex.html']/span", 'Module Index'), + (".//li/a[@href='search.html']/span", 'Search Page'), + # custom sidebar only for contents + (".//h4", 'Contents sidebar'), + # custom JavaScript + (".//script[@src='file://moo.js']", ''), + # URL in contents + (".//a[@class='reference external'][@href='http://sphinx-doc.org/']", + 'http://sphinx-doc.org/'), + (".//a[@class='reference external'][@href='http://sphinx-doc.org/latest/']", + 'Latest reference'), + # Indirect hyperlink targets across files + (".//a[@href='markup.html#some-label'][@class='reference internal']/span", + '^indirect hyperref$'), + ], + 'bom.html': [ + (".//title", " File with UTF-8 BOM"), + ], + 'extensions.html': [ + (".//a[@href='http://python.org/dev/']", "http://python.org/dev/"), + (".//a[@href='http://bugs.python.org/issue1000']", "issue 1000"), + (".//a[@href='http://bugs.python.org/issue1042']", "explicit caption"), + ], + '_static/statictmpl.html': [ + (".//project", 'Sphinx '), + ], + 'genindex.html': [ + # index entries + (".//a/strong", "Main"), + (".//a/strong", "[1]"), + (".//a/strong", "Other"), + (".//a", "entry"), + (".//li/a", "double"), + ], + 'footnote.html': [ + (".//a[@class='footnote-reference'][@href='#id7'][@id='id1']", r"\[1\]"), + (".//a[@class='footnote-reference'][@href='#id8'][@id='id2']", r"\[2\]"), + (".//a[@class='footnote-reference'][@href='#foo'][@id='id3']", r"\[3\]"), + (".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"), + (".//a[@class='footnote-reference'][@href='#id9'][@id='id5']", r"\[4\]"), + (".//a[@class='footnote-reference'][@href='#id10'][@id='id6']", r"\[5\]"), + (".//a[@class='fn-backref'][@href='#id1']", r"\[1\]"), + (".//a[@class='fn-backref'][@href='#id2']", r"\[2\]"), + (".//a[@class='fn-backref'][@href='#id3']", r"\[3\]"), + (".//a[@class='fn-backref'][@href='#id4']", r"\[bar\]"), + (".//a[@class='fn-backref'][@href='#id5']", r"\[4\]"), + (".//a[@class='fn-backref'][@href='#id6']", r"\[5\]"), + ], + 'otherext.html': [ + (".//h1", "Generated section"), + (".//a[@href='_sources/otherext.foo.txt']", ''), + ] +})) +@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={ + 'html_context.hckey_co': 'hcval_co'}) +@pytest.mark.test_params(shared_result='test_build_html_output') +def test_html_output(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) + + +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//li[@class='toctree-l3']/a", '1.1.1. Foo A1', True), + (".//li[@class='toctree-l3']/a", '1.2.1. Foo B1', True), + (".//li[@class='toctree-l3']/a", '2.1.1. Bar A1', False), + (".//li[@class='toctree-l3']/a", '2.2.1. Bar B1', False), + ], + 'foo.html': [ + (".//h1", '1. Foo', True), + (".//h2", '1.1. Foo A', True), + (".//h3", '1.1.1. Foo A1', True), + (".//h2", '1.2. Foo B', True), + (".//h3", '1.2.1. Foo B1', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '1.1. Foo A', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '1.1.1. Foo A1', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '1.2. Foo B', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '1.2.1. Foo B1', True), + ], + 'bar.html': [ + (".//h1", '2. Bar', True), + (".//h2", '2.1. Bar A', True), + (".//h2", '2.2. Bar B', True), + (".//h3", '2.2.1. Bar B1', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '2. Bar', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '2.1. Bar A', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '2.2. Bar B', True), + (".//div[@class='sphinxsidebarwrapper']//li/a", '2.2.1. Bar B1', False), + ], + 'baz.html': [ + (".//h1", '2.1.1. Baz A', True), + ], +})) +@pytest.mark.sphinx('html', testroot='tocdepth') +@pytest.mark.test_params(shared_result='test_build_html_tocdepth') +def test_tocdepth(app, cached_etree_parse, fname, expect): + app.build() # issue #1251 - app.builder.build_all() - - expects = { - 'index.html': [ - (".//li[@class='toctree-l3']/a", '1.1.1. Foo A1', True), - (".//li[@class='toctree-l3']/a", '1.2.1. Foo B1', True), - (".//li[@class='toctree-l3']/a", '2.1.1. Bar A1', False), - (".//li[@class='toctree-l3']/a", '2.2.1. Bar B1', False), - ], - 'foo.html': [ - (".//h1", '1. Foo', True), - (".//h2", '1.1. Foo A', True), - (".//h3", '1.1.1. Foo A1', True), - (".//h2", '1.2. Foo B', True), - (".//h3", '1.2.1. Foo B1', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '1.1. Foo A', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '1.1.1. Foo A1', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '1.2. Foo B', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '1.2.1. Foo B1', True), - ], - 'bar.html': [ - (".//h1", '2. Bar', True), - (".//h2", '2.1. Bar A', True), - (".//h2", '2.2. Bar B', True), - (".//h3", '2.2.1. Bar B1', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '2. Bar', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '2.1. Bar A', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '2.2. Bar B', True), - (".//div[@class='sphinxsidebarwrapper']//li/a", '2.2.1. Bar B1', False), - ], - 'baz.html': [ - (".//h1", '2.1.1. Baz A', True), - ], - } - - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='singlehtml', testroot='tocdepth') -def test_tocdepth_singlehtml(app, status, warning): - app.builder.build_all() +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//li[@class='toctree-l3']/a", '1.1.1. Foo A1', True), + (".//li[@class='toctree-l3']/a", '1.2.1. Foo B1', True), + (".//li[@class='toctree-l3']/a", '2.1.1. Bar A1', False), + (".//li[@class='toctree-l3']/a", '2.2.1. Bar B1', False), - expects = { - 'index.html': [ - (".//li[@class='toctree-l3']/a", '1.1.1. Foo A1', True), - (".//li[@class='toctree-l3']/a", '1.2.1. Foo B1', True), - (".//li[@class='toctree-l3']/a", '2.1.1. Bar A1', False), - (".//li[@class='toctree-l3']/a", '2.2.1. Bar B1', False), + # index.rst + (".//h1", 'test-tocdepth', True), - # index.rst - (".//h1", 'test-tocdepth', True), + # foo.rst + (".//h2", '1. Foo', True), + (".//h3", '1.1. Foo A', True), + (".//h4", '1.1.1. Foo A1', True), + (".//h3", '1.2. Foo B', True), + (".//h4", '1.2.1. Foo B1', True), - # foo.rst - (".//h2", '1. Foo', True), - (".//h3", '1.1. Foo A', True), - (".//h4", '1.1.1. Foo A1', True), - (".//h3", '1.2. Foo B', True), - (".//h4", '1.2.1. Foo B1', True), + # bar.rst + (".//h2", '2. Bar', True), + (".//h3", '2.1. Bar A', True), + (".//h3", '2.2. Bar B', True), + (".//h4", '2.2.1. Bar B1', True), - # bar.rst - (".//h2", '2. Bar', True), - (".//h3", '2.1. Bar A', True), - (".//h3", '2.2. Bar B', True), - (".//h4", '2.2.1. Bar B1', True), - - # baz.rst - (".//h4", '2.1.1. Baz A', True), - ], - } - - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found + # baz.rst + (".//h4", '2.1.1. Baz A', True), + ], +})) +@pytest.mark.sphinx('singlehtml', testroot='tocdepth') +@pytest.mark.test_params(shared_result='test_build_html_tocdepth') +def test_tocdepth_singlehtml(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='html', testroot='numfig') -def test_numfig_disabled(app, status, warning): - app.builder.build_all() - +@pytest.mark.sphinx('html', testroot='numfig') +@pytest.mark.test_params(shared_result='test_build_html_numfig') +def test_numfig_disabled_warn(app, warning): + app.build() warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' in warnings assert 'index.rst:55: WARNING: no number is assigned for section: index' not in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' not in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' not in warnings - expects = { - 'index.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", None, True), - (".//table/caption/span[@class='caption-number']", None, True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", None, True), - (".//li/code/span", '^fig1$', True), - (".//li/code/span", '^Figure%s$', True), - (".//li/code/span", '^table-1$', True), - (".//li/code/span", '^Table:%s$', True), - (".//li/code/span", '^CODE_1$', True), - (".//li/code/span", '^Code-%s$', True), - (".//li/code/span", '^foo$', True), - (".//li/code/span", '^bar_a$', True), - (".//li/code/span", '^Fig.{number}$', True), - (".//li/code/span", '^Sect.{number}$', True), - ], - 'foo.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", None, True), - (".//table/caption/span[@class='caption-number']", None, True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", None, True), - ], - 'bar.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", None, True), - (".//table/caption/span[@class='caption-number']", None, True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", None, True), - ], - 'baz.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", None, True), - (".//table/caption/span[@class='caption-number']", None, True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", None, True), - ], - } - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", None, True), + (".//table/caption/span[@class='caption-number']", None, True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", None, True), + (".//li/code/span", '^fig1$', True), + (".//li/code/span", '^Figure%s$', True), + (".//li/code/span", '^table-1$', True), + (".//li/code/span", '^Table:%s$', True), + (".//li/code/span", '^CODE_1$', True), + (".//li/code/span", '^Code-%s$', True), + (".//li/code/span", '^foo$', True), + (".//li/code/span", '^bar_a$', True), + (".//li/code/span", '^Fig.{number}$', True), + (".//li/code/span", '^Sect.{number}$', True), + ], + 'foo.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", None, True), + (".//table/caption/span[@class='caption-number']", None, True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", None, True), + ], + 'bar.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", None, True), + (".//table/caption/span[@class='caption-number']", None, True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", None, True), + ], + 'baz.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", None, True), + (".//table/caption/span[@class='caption-number']", None, True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", None, True), + ], +})) +@pytest.mark.sphinx('html', testroot='numfig') +@pytest.mark.test_params(shared_result='test_build_html_numfig') +def test_numfig_disabled(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='html', testroot='numfig', freshenv=True, - confoverrides={'numfig': True}) -def test_numfig_without_numbered_toctree(app, status, warning): +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) +@pytest.mark.test_params(specific_srcdir=True) +def test_numfig_without_numbered_toctree_warn(app, warning): + app.build() # remove :numbered: option index = (app.srcdir / 'index.rst').text() index = re.sub(':numbered:.*', '', index, re.MULTILINE) @@ -571,522 +586,514 @@ def test_numfig_without_numbered_toctree(app, status, warning): assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings - expects = { - 'index.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 9 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 10 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 9 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 10 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 9 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 10 $', True), - (".//li/a/span", '^Fig. 9$', True), - (".//li/a/span", '^Figure6$', True), - (".//li/a/span", '^Table 9$', True), - (".//li/a/span", '^Table:6$', True), - (".//li/a/span", '^Listing 9$', True), - (".//li/a/span", '^Code-6$', True), - (".//li/code/span", '^foo$', True), - (".//li/code/span", '^bar_a$', True), - (".//li/a/span", '^Fig.9 should be Fig.1$', True), - (".//li/code/span", '^Sect.{number}$', True), - ], - 'foo.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 4 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 4 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 4 $', True), - ], - 'bar.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 5 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 7 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 8 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 5 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 7 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 8 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 5 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 7 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 8 $', True), - ], - 'baz.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 6 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 6 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 6 $', True), - ], - } - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 9 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 10 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 9 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 10 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 9 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 10 $', True), + (".//li/a/span", '^Fig. 9$', True), + (".//li/a/span", '^Figure6$', True), + (".//li/a/span", '^Table 9$', True), + (".//li/a/span", '^Table:6$', True), + (".//li/a/span", '^Listing 9$', True), + (".//li/a/span", '^Code-6$', True), + (".//li/code/span", '^foo$', True), + (".//li/code/span", '^bar_a$', True), + (".//li/a/span", '^Fig.9 should be Fig.1$', True), + (".//li/code/span", '^Sect.{number}$', True), + ], + 'foo.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 4 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 4 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 4 $', True), + ], + 'bar.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 5 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 7 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 8 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 5 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 7 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 8 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 5 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 7 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 8 $', True), + ], + 'baz.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 6 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 6 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 6 $', True), + ], +})) +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) +@pytest.mark.test_params(specific_srcdir=True) +def test_numfig_without_numbered_toctree(app, cached_etree_parse, fname, expect): + # remove :numbered: option + index = (app.srcdir / 'index.rst').text() + index = re.sub(':numbered:.*', '', index, re.MULTILINE) + (app.srcdir / 'index.rst').write_text(index, encoding='utf-8') - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found + if not app.outdir.listdir(): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='html', testroot='numfig', srcdir='test_build_html_numfig_on', - confoverrides={'numfig': True}) -def test_numfig_with_numbered_toctree(app, status, warning): - app.builder.build_all() - +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) +@pytest.mark.test_params(shared_result='test_build_html_numfig_on') +def test_numfig_with_numbered_toctree_warn(app, warning): + app.build() warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings - expects = { - 'index.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2 $', True), - (".//li/a/span", '^Fig. 1$', True), - (".//li/a/span", '^Figure2.2$', True), - (".//li/a/span", '^Table 1$', True), - (".//li/a/span", '^Table:2.2$', True), - (".//li/a/span", '^Listing 1$', True), - (".//li/a/span", '^Code-2.2$', True), - (".//li/a/span", '^Section.1$', True), - (".//li/a/span", '^Section.2.1$', True), - (".//li/a/span", '^Fig.1 should be Fig.1$', True), - (".//li/a/span", '^Sect.1 Foo$', True), - ], - 'foo.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.2 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.4 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.4 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.4 $', True), - ], - 'bar.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.4 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.4 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.4 $', True), - ], - 'baz.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.2 $', True), - ], - } - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2 $', True), + (".//li/a/span", '^Fig. 1$', True), + (".//li/a/span", '^Figure2.2$', True), + (".//li/a/span", '^Table 1$', True), + (".//li/a/span", '^Table:2.2$', True), + (".//li/a/span", '^Listing 1$', True), + (".//li/a/span", '^Code-2.2$', True), + (".//li/a/span", '^Section.1$', True), + (".//li/a/span", '^Section.2.1$', True), + (".//li/a/span", '^Fig.1 should be Fig.1$', True), + (".//li/a/span", '^Sect.1 Foo$', True), + ], + 'foo.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.2 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.4 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.4 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.4 $', True), + ], + 'bar.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.4 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.4 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.4 $', True), + ], + 'baz.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.2 $', True), + ], +})) +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) +@pytest.mark.test_params(shared_result='test_build_html_numfig_on') +def test_numfig_with_numbered_toctree(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='html', testroot='numfig', - srcdir='test_build_html_numfig_format_warn', - confoverrides={'numfig': True, - 'numfig_format': {'figure': 'Figure:%s', - 'table': 'Tab_%s', - 'code-block': 'Code-%s', - 'section': 'SECTION-%s'}}) -def test_numfig_with_prefix(app, status, warning): - app.builder.build_all() - +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ + 'numfig': True, + 'numfig_format': {'figure': 'Figure:%s', + 'table': 'Tab_%s', + 'code-block': 'Code-%s', + 'section': 'SECTION-%s'}}) +@pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn') +def test_numfig_with_prefix_warn(app, warning): + app.build() warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings - expects = { - 'index.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-2 $', True), - (".//li/a/span", '^Figure:1$', True), - (".//li/a/span", '^Figure2.2$', True), - (".//li/a/span", '^Tab_1$', True), - (".//li/a/span", '^Table:2.2$', True), - (".//li/a/span", '^Code-1$', True), - (".//li/a/span", '^Code-2.2$', True), - (".//li/a/span", '^SECTION-1$', True), - (".//li/a/span", '^SECTION-2.1$', True), - (".//li/a/span", '^Fig.1 should be Fig.1$', True), - (".//li/a/span", '^Sect.1 Foo$', True), - ], - 'foo.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:1.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:1.2 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:1.3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:1.4 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_1.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_1.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_1.3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_1.4 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-1.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-1.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-1.3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-1.4 $', True), - ], - 'bar.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:2.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:2.3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:2.4 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_2.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_2.3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_2.4 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-2.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-2.3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-2.4 $', True), - ], - 'baz.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Figure:2.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Tab_2.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Code-2.2 $', True), - ], - } - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-2 $', True), + (".//li/a/span", '^Figure:1$', True), + (".//li/a/span", '^Figure2.2$', True), + (".//li/a/span", '^Tab_1$', True), + (".//li/a/span", '^Table:2.2$', True), + (".//li/a/span", '^Code-1$', True), + (".//li/a/span", '^Code-2.2$', True), + (".//li/a/span", '^SECTION-1$', True), + (".//li/a/span", '^SECTION-2.1$', True), + (".//li/a/span", '^Fig.1 should be Fig.1$', True), + (".//li/a/span", '^Sect.1 Foo$', True), + ], + 'foo.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:1.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:1.2 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:1.3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:1.4 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_1.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_1.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_1.3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_1.4 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-1.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-1.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-1.3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-1.4 $', True), + ], + 'bar.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:2.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:2.3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:2.4 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_2.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_2.3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_2.4 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-2.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-2.3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-2.4 $', True), + ], + 'baz.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Figure:2.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Tab_2.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Code-2.2 $', True), + ], +})) +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ + 'numfig': True, + 'numfig_format': {'figure': 'Figure:%s', + 'table': 'Tab_%s', + 'code-block': 'Code-%s', + 'section': 'SECTION-%s'}}) +@pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn') +def test_numfig_with_prefix(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='html', testroot='numfig', - srcdir='test_build_html_numfig_depth_2', - confoverrides={'numfig': True, 'numfig_secnum_depth': 2}) -def test_numfig_with_secnum_depth(app, status, warning): - app.builder.build_all() - +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ + 'numfig': True, 'numfig_secnum_depth': 2}) +@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') +def test_numfig_with_secnum_depth_warn(app, warning): + app.build() warnings = warning.getvalue() assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings - expects = { - 'index.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2 $', True), - (".//li/a/span", '^Fig. 1$', True), - (".//li/a/span", '^Figure2.1.2$', True), - (".//li/a/span", '^Table 1$', True), - (".//li/a/span", '^Table:2.1.2$', True), - (".//li/a/span", '^Listing 1$', True), - (".//li/a/span", '^Code-2.1.2$', True), - (".//li/a/span", '^Section.1$', True), - (".//li/a/span", '^Section.2.1$', True), - (".//li/a/span", '^Fig.1 should be Fig.1$', True), - (".//li/a/span", '^Sect.1 Foo$', True), - ], - 'foo.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.1.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.1.2 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.2.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.1.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.1.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.2.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.1.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.1.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.2.1 $', True), - ], - 'bar.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.1.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.1.3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.2.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.1.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.1.3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.2.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.1.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.1.3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.2.1 $', True), - ], - 'baz.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.1.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.1.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.1.2 $', True), - ], - } - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2 $', True), + (".//li/a/span", '^Fig. 1$', True), + (".//li/a/span", '^Figure2.1.2$', True), + (".//li/a/span", '^Table 1$', True), + (".//li/a/span", '^Table:2.1.2$', True), + (".//li/a/span", '^Listing 1$', True), + (".//li/a/span", '^Code-2.1.2$', True), + (".//li/a/span", '^Section.1$', True), + (".//li/a/span", '^Section.2.1$', True), + (".//li/a/span", '^Fig.1 should be Fig.1$', True), + (".//li/a/span", '^Sect.1 Foo$', True), + ], + 'foo.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.1.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.1.2 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.2.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.1.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.1.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.2.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.1.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.1.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.2.1 $', True), + ], + 'bar.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.1.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.1.3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.2.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.1.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.1.3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.2.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.1.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.1.3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.2.1 $', True), + ], + 'baz.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.1.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.1.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.1.2 $', True), + ], +})) +@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ + 'numfig': True, 'numfig_secnum_depth': 2}) +@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') +def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='singlehtml', testroot='numfig', - srcdir='test_build_html_numfig_on', - confoverrides={'numfig': True}) -def test_numfig_with_singlehtml(app, status, warning): - app.builder.build_all() - - expects = { - 'index.html': [ - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2 $', True), - (".//li/a/span", '^Fig. 1$', True), - (".//li/a/span", '^Figure2.2$', True), - (".//li/a/span", '^Table 1$', True), - (".//li/a/span", '^Table:2.2$', True), - (".//li/a/span", '^Listing 1$', True), - (".//li/a/span", '^Code-2.2$', True), - (".//li/a/span", '^Section.1$', True), - (".//li/a/span", '^Section.2.1$', True), - (".//li/a/span", '^Fig.1 should be Fig.1$', True), - (".//li/a/span", '^Sect.1 Foo$', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.2 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 1.4 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 1.4 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 1.4 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.1 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.3 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.4 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.1 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.3 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.4 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.1 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.3 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.4 $', True), - (".//div[@class='figure']/p[@class='caption']/" - "span[@class='caption-number']", '^Fig. 2.2 $', True), - (".//table/caption/span[@class='caption-number']", - '^Table 2.2 $', True), - (".//div[@class='code-block-caption']/" - "span[@class='caption-number']", '^Listing 2.2 $', True), - ], - } - - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2 $', True), + (".//li/a/span", '^Fig. 1$', True), + (".//li/a/span", '^Figure2.2$', True), + (".//li/a/span", '^Table 1$', True), + (".//li/a/span", '^Table:2.2$', True), + (".//li/a/span", '^Listing 1$', True), + (".//li/a/span", '^Code-2.2$', True), + (".//li/a/span", '^Section.1$', True), + (".//li/a/span", '^Section.2.1$', True), + (".//li/a/span", '^Fig.1 should be Fig.1$', True), + (".//li/a/span", '^Sect.1 Foo$', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.2 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 1.4 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 1.4 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 1.4 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.1 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.3 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.4 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.1 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.3 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.4 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.1 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.3 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.4 $', True), + (".//div[@class='figure']/p[@class='caption']/" + "span[@class='caption-number']", '^Fig. 2.2 $', True), + (".//table/caption/span[@class='caption-number']", + '^Table 2.2 $', True), + (".//div[@class='code-block-caption']/" + "span[@class='caption-number']", '^Listing 2.2 $', True), + ], +})) +@pytest.mark.sphinx('singlehtml', testroot='numfig', confoverrides={ + 'numfig': True}) +@pytest.mark.test_params(shared_result='test_build_html_numfig_on') +def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@gen_with_app(buildername='html', testroot='add_enumerable_node') -def test_enumerable_node(app, status, warning): - app.builder.build_all() - - expects = { - 'index.html': [ - (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", - "Fig. 1", True), - (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", - "Fig. 2", True), - (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", - "Fig. 3", True), - (".//div//span[@class='caption-number']", "No.1 ", True), - (".//div//span[@class='caption-number']", "No.2 ", True), - (".//li/a/span", 'Fig. 1', True), - (".//li/a/span", 'Fig. 2', True), - (".//li/a/span", 'Fig. 3', True), - (".//li/a/span", 'No.1', True), - (".//li/a/span", 'No.2', True), - ], - } - - for fname, paths in iteritems(expects): - with (app.outdir / fname).open('rb') as fp: - etree = HTML_PARSER.parse(fp) - - for xpath, check, be_found in paths: - yield check_xpath, etree, fname, xpath, check, be_found +@pytest.mark.parametrize("fname,expect", flat_dict({ + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", + "Fig. 1", True), + (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", + "Fig. 2", True), + (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", + "Fig. 3", True), + (".//div//span[@class='caption-number']", "No.1 ", True), + (".//div//span[@class='caption-number']", "No.2 ", True), + (".//li/a/span", 'Fig. 1', True), + (".//li/a/span", 'Fig. 2', True), + (".//li/a/span", 'Fig. 3', True), + (".//li/a/span", 'No.1', True), + (".//li/a/span", 'No.2', True), + ], +})) +@pytest.mark.sphinx('html', testroot='add_enumerable_node') +@pytest.mark.test_params(specific_srcdir=True) +def test_enumerable_node(app, cached_etree_parse, fname, expect): + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) @pytest.mark.sphinx('html', testroot='html_assets') -def test_html_assets(app, status, warning): +def test_html_assets(app): app.builder.build_all() # html_static_path @@ -1114,7 +1121,7 @@ def test_html_assets(app, status, warning): @pytest.mark.sphinx('html', confoverrides={'html_sourcelink_suffix': ''}) -def test_html_sourcelink_suffix(app, status, warning): +def test_html_sourcelink_suffix(app): app.builder.build_all() content_otherext = (app.outdir / 'otherext.html').text() content_images = (app.outdir / 'images.html').text() diff --git a/tests/test_config.py b/tests/test_config.py index aef5ed846..4bf4d6c11 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,9 +13,6 @@ from six import PY3, iteritems import pytest import mock -from util import TestApp, gen_with_app, \ - assert_in, assert_not_in - import sphinx from sphinx.config import Config from sphinx.errors import ExtensionError, ConfigError, VersionRequirementError @@ -76,7 +73,6 @@ def test_core_config(app, status, warning): assert cfg['project'] == cfg.project == 'Sphinx Tests' -@pytest.mark.sphinx() def test_extension_values(app, status, warning): cfg = app.config @@ -126,39 +122,39 @@ def test_errors_warnings(tempdir): assert warned[0] -def test_errors_if_setup_is_not_callable(tempdir): +def test_errors_if_setup_is_not_callable(tempdir, make_app): # test the error to call setup() in the config file (tempdir / 'conf.py').write_text(u'setup = 1') with pytest.raises(ConfigError) as excinfo: - TestApp(srcdir=tempdir) + make_app(srcdir=tempdir) assert 'callable' in str(excinfo.value) @mock.patch.object(sphinx, '__display_version__', '1.3.4') -def test_needs_sphinx(): +def test_needs_sphinx(make_app): # micro version - app = TestApp(confoverrides={'needs_sphinx': '1.3.3'}) # OK: less + app = make_app(confoverrides={'needs_sphinx': '1.3.3'}) # OK: less app.cleanup() - app = TestApp(confoverrides={'needs_sphinx': '1.3.4'}) # OK: equals + app = make_app(confoverrides={'needs_sphinx': '1.3.4'}) # OK: equals app.cleanup() with pytest.raises(VersionRequirementError): - TestApp(confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater + make_app(confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater # minor version - app = TestApp(confoverrides={'needs_sphinx': '1.2'}) # OK: less + app = make_app(confoverrides={'needs_sphinx': '1.2'}) # OK: less app.cleanup() - app = TestApp(confoverrides={'needs_sphinx': '1.3'}) # OK: equals + app = make_app(confoverrides={'needs_sphinx': '1.3'}) # OK: equals app.cleanup() with pytest.raises(VersionRequirementError): - TestApp(confoverrides={'needs_sphinx': '1.4'}) # NG: greater + make_app(confoverrides={'needs_sphinx': '1.4'}) # NG: greater # major version - app = TestApp(confoverrides={'needs_sphinx': '0'}) # OK: less + app = make_app(confoverrides={'needs_sphinx': '0'}) # OK: less app.cleanup() - app = TestApp(confoverrides={'needs_sphinx': '1'}) # OK: equals + app = make_app(confoverrides={'needs_sphinx': '1'}) # OK: equals app.cleanup() with pytest.raises(VersionRequirementError): - TestApp(confoverrides={'needs_sphinx': '2'}) # NG: greater + make_app(confoverrides={'needs_sphinx': '2'}) # NG: greater def test_config_eol(tempdir): @@ -176,12 +172,14 @@ def test_config_eol(tempdir): 'primary_domain': None}) def test_builtin_conf(app, status, warning): warnings = warning.getvalue() - assert_in('master_doc', warnings, - 'override on builtin "master_doc" should raise a type warning') - assert_not_in('language', warnings, 'explicitly permitted ' - 'override on builtin "language" should NOT raise a type warning') - assert_not_in('primary_domain', warnings, 'override to None on builtin ' - '"primary_domain" should NOT raise a type warning') + assert 'master_doc' in warnings, ( + 'override on builtin "master_doc" should raise a type warning') + assert 'language' not in warnings, ( + 'explicitly permitted override on builtin "language" should NOT raise ' + 'a type warning') + assert 'primary_domain' not in warnings, ( + 'override to None on builtin "primary_domain" should NOT raise a type ' + 'warning') # See roots/test-config/conf.py. @@ -196,7 +194,7 @@ TYPECHECK_WARNINGS = { 'value8': False, 'value9': False, 'value10': False, - 'value11': True, + 'value11': False if PY3 else True, 'value12': False, 'value13': False, 'value14': False, @@ -205,15 +203,17 @@ TYPECHECK_WARNINGS = { } -@gen_with_app(testroot='config') -def test_gen_check_types(app, status, warning): - if PY3: - TYPECHECK_WARNINGS['value11'] = False - - for key, should in iteritems(TYPECHECK_WARNINGS): - yield assert_in if should else assert_not_in, key, warning.getvalue(), ( - 'override on "%s" should%s raise a type warning' % - (key, '' if should else ' NOT') +@pytest.mark.parametrize("key,should", iteritems(TYPECHECK_WARNINGS)) +@pytest.mark.sphinx(testroot='config') +def test_check_types(warning, key, should): + warn = warning.getvalue() + if should: + assert key in warn, ( + 'override on "%s" should raise a type warning' % key + ) + else: + assert key not in warn, ( + 'override on "%s" should NOT raise a type warning' % key ) diff --git a/tests/test_correct_year.py b/tests/test_correct_year.py index 2c2f0d812..574adb6f0 100644 --- a/tests/test_correct_year.py +++ b/tests/test_correct_year.py @@ -8,42 +8,30 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import os - -from util import TestApp +import pytest -def test_correct_year(): - try: - # save current value of SOURCE_DATE_EPOCH - sde = os.environ.pop('SOURCE_DATE_EPOCH', None) - +@pytest.fixture( + params=[ # test with SOURCE_DATE_EPOCH unset: no modification - app = TestApp(buildername='html', testroot='correct-year') - app.builder.build_all() - content = (app.outdir / 'contents.html').text() - app.cleanup() - assert '2006-2009' in content + (None, '2006-2009'), + # test with SOURCE_DATE_EPOCH set: copyright year should be updated + ('1293840000', '2006-2011'), + ('1293839999', '2006-2010'), + ], - # test with SOURCE_DATE_EPOCH set: copyright year should be - # updated - os.environ['SOURCE_DATE_EPOCH'] = "1293840000" - app = TestApp(buildername='html', testroot='correct-year') - app.builder.build_all() - content = (app.outdir / 'contents.html').text() - app.cleanup() - assert '2006-2011' in content +) +def expect_date(request, monkeypatch): + sde, expect = request.param + if sde: + monkeypatch.setenv('SOURCE_DATE_EPOCH', sde) + else: + monkeypatch.delenv('SOURCE_DATE_EPOCH', raising=False) + yield expect - os.environ['SOURCE_DATE_EPOCH'] = "1293839999" - app = TestApp(buildername='html', testroot='correct-year') - app.builder.build_all() - content = (app.outdir / 'contents.html').text() - app.cleanup() - assert '2006-2010' in content - finally: - # Restores SOURCE_DATE_EPOCH - if sde is None: - os.environ.pop('SOURCE_DATE_EPOCH', None) - else: - os.environ['SOURCE_DATE_EPOCH'] = sde +@pytest.mark.sphinx('html', testroot='correct-year') +def test_correct_year(expect_date, app): + app.build() + content = (app.outdir / 'contents.html').text() + assert expect_date in content diff --git a/tests/test_environment.py b/tests/test_environment.py index 232b6e28b..476b66923 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -9,9 +9,7 @@ :license: BSD, see LICENSE for details. """ -from six import PY3 - -from util import TestApp, remove_unicode_literals, path +from util import SphinxTestApp, path from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.latex import LaTeXBuilder @@ -22,7 +20,7 @@ warnings = [] def setup_module(): global app, env - app = TestApp(srcdir='root-envtest') + app = SphinxTestApp(srcdir='root-envtest') env = app.env env.set_warnfunc(lambda *args, **kwargs: warnings.append(args)) diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py index 037109126..595f922ae 100644 --- a/tests/test_environment_toctree.py +++ b/tests/test_environment_toctree.py @@ -16,23 +16,13 @@ from sphinx.addnodes import compact_paragraph, only from sphinx.builders.html import StandaloneHTMLBuilder import pytest -from util import gen_with_app, assert_node +from util import assert_node -@gen_with_app('xml', testroot='toctree') -def test_basic(app, status, warning): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_process_doc(app): app.build() - yield _test_process_doc, app - yield _test_get_toc_for, app - yield _test_get_toc_for_only, app - yield _test_get_toc_for_tocdepth, app - yield _test_get_toctree_for, app - yield _test_get_toctree_for_collapse, app - yield _test_get_toctree_for_maxdepth, app - yield _test_get_toctree_for_includehidden, app - - -def _test_process_doc(app): # tocs toctree = app.env.tocs['index'] assert_node(toctree, @@ -99,7 +89,7 @@ def _test_process_doc(app): @pytest.mark.sphinx('dummy', testroot='toctree-glob') -def test_glob(app, status, warning): +def test_glob(app): includefiles = ['foo', 'bar/index', 'bar/bar_1', 'bar/bar_2', 'bar/bar_3', 'baz', 'qux/index'] @@ -144,7 +134,10 @@ def test_glob(app, status, warning): assert app.env.numbered_toctrees == set() -def _test_get_toc_for(app): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_get_toc_for(app): + app.build() toctree = app.env.get_toc_for('index', app.builder) assert_node(toctree, @@ -167,7 +160,10 @@ def _test_get_toc_for(app): [compact_paragraph, reference, "Indices and tables"]) -def _test_get_toc_for_only(app): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_get_toc_for_only(app): + app.build() builder = StandaloneHTMLBuilder(app) toctree = app.env.get_toc_for('index', builder) @@ -194,7 +190,10 @@ def _test_get_toc_for_only(app): [compact_paragraph, reference, "Indices and tables"]) -def _test_get_toc_for_tocdepth(app): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_get_toc_for_tocdepth(app): + app.build() toctree = app.env.get_toc_for('tocdepth', app.builder) assert_node(toctree, @@ -206,7 +205,10 @@ def _test_get_toc_for_tocdepth(app): [bullet_list, list_item, compact_paragraph, reference, "level 2"]) -def _test_get_toctree_for(app): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_get_toctree_for(app): + app.build() toctree = app.env.get_toctree_for('index', app.builder, collapse=False) assert_node(toctree, [compact_paragraph, ([caption, "Table of Contents"], @@ -240,7 +242,10 @@ def _test_get_toctree_for(app): assert_node(toctree[3][1][0][0], reference, refuri="http://python.org/") -def _test_get_toctree_for_collapse(app): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_get_toctree_for_collapse(app): + app.build() toctree = app.env.get_toctree_for('index', app.builder, collapse=True) assert_node(toctree, [compact_paragraph, ([caption, "Table of Contents"], @@ -265,7 +270,10 @@ def _test_get_toctree_for_collapse(app): assert_node(toctree[3][1][0][0], reference, refuri="http://python.org/") -def _test_get_toctree_for_maxdepth(app): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_get_toctree_for_maxdepth(app): + app.build() toctree = app.env.get_toctree_for('index', app.builder, collapse=False, maxdepth=3) assert_node(toctree, [compact_paragraph, ([caption, "Table of Contents"], @@ -304,7 +312,10 @@ def _test_get_toctree_for_maxdepth(app): assert_node(toctree[3][1][0][0], reference, refuri="http://python.org/") -def _test_get_toctree_for_includehidden(app): +@pytest.mark.sphinx('xml', testroot='toctree') +@pytest.mark.test_params(shared_result='test_environment_toctree_basic') +def test_get_toctree_for_includehidden(app): + app.build() toctree = app.env.get_toctree_for('index', app.builder, collapse=False, includehidden=False) assert_node(toctree, diff --git a/tests/test_intl.py b/tests/test_intl.py index cb9d2cecf..1372c80d8 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -15,31 +15,22 @@ import os import re import pickle from docutils import nodes -from subprocess import Popen, PIPE -from babel.messages import pofile +from babel.messages import pofile, mofile from six import string_types import pytest -from util import tempdir, rootdir, path, gen_with_app, SkipTest, \ - assert_re_search, assert_not_re_search, assert_in, assert_not_in, \ - assert_startswith, assert_node, etree_parse, assert_equal +from util import tempdir, rootdir, path, assert_re_search, \ + assert_not_re_search, assert_startswith, assert_node, etree_parse -root = tempdir / 'test-intl' - - -def gen_with_intl_app(builder, confoverrides={}, *args, **kw): - default_kw = { - 'testroot': 'intl', - 'confoverrides': { - 'language': 'xx', 'locale_dirs': ['.'], - 'gettext_compact': False, - }, - } - default_kw.update(kw) - default_kw['confoverrides'].update(confoverrides) - return gen_with_app(builder, *args, **default_kw) +sphinx_intl = pytest.mark.sphinx( + testroot='intl', + confoverrides={ + 'language': 'xx', 'locale_dirs': ['.'], + 'gettext_compact': False, + }, +) def read_po(pathname): @@ -47,32 +38,41 @@ def read_po(pathname): return pofile.read_po(f) -def setup_module(): - if not root.exists(): - (rootdir / 'roots' / 'test-intl').copytree(root) - # Delete remnants left over after failed build - # Compile all required catalogs into binary format (*.mo). - for dirpath, dirs, files in os.walk(root): - dirpath = path(dirpath) - for f in [f for f in files if f.endswith('.po')]: - po = dirpath / f - mo = root / 'xx' / 'LC_MESSAGES' / ( - os.path.relpath(po[:-3], root) + '.mo') - if not mo.parent.exists(): - mo.parent.makedirs() - try: - p = Popen(['msgfmt', po, '-o', mo], - stdout=PIPE, stderr=PIPE) - except OSError: - raise SkipTest # most likely msgfmt was not found - else: - stdout, stderr = p.communicate() - if p.returncode != 0: - print(stdout) - print(stderr) - assert False, \ - 'msgfmt exited with return code %s' % p.returncode - assert mo.isfile(), 'msgfmt failed' +def write_mo(pathname, po): + with pathname.open('wb') as f: + return mofile.write_mo(f, po) + + +@pytest.fixture +def build_mo(): + def builder(srcdir): + """ + :param str srcdir: app.srcdir + """ + srcdir = path(srcdir) + for dirpath, dirs, files in os.walk(srcdir): + dirpath = path(dirpath) + for f in [f for f in files if f.endswith('.po')]: + po = dirpath / f + mo = srcdir / 'xx' / 'LC_MESSAGES' / ( + os.path.relpath(po[:-3], srcdir) + '.mo') + if not mo.parent.exists(): + mo.parent.makedirs() + + write_mo(mo, read_po(po)) + return builder + + +@pytest.fixture(autouse=True) +def setup_intl(app_params, build_mo): + build_mo(app_params.kwargs['srcdir']) + + +@pytest.fixture(autouse=True) +def _info(app): + yield + print('# language:', app.config.language) + print('# locale_dirs:', app.config.locale_dirs) def elem_gettexts(elem): @@ -110,46 +110,72 @@ def assert_elem(elem, texts=None, refs=None, names=None): def assert_count(expected_expr, result, count): find_pair = (expected_expr, result) - return assert_equal, len(re.findall(*find_pair)), count, find_pair + assert len(re.findall(*find_pair)) == count, find_pair -@gen_with_intl_app('text', freshenv=True) -def test_text_builder(app, status, warning): - app.builder.build_all() - - # --- toctree - +@sphinx_intl +@pytest.mark.sphinx('text') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_text_toctree(app): + app.build() result = (app.outdir / 'contents.txt').text(encoding='utf-8') - yield assert_startswith, result, u"CONTENTS\n********\n\nTABLE OF CONTENTS\n" + assert_startswith(result, u"CONTENTS\n********\n\nTABLE OF CONTENTS\n") - # --- warnings in translation +@sphinx_intl +@pytest.mark.sphinx('text') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_text_emit_warnings(app, warning): + app.build() + # test warnings in translation warnings = getwarning(warning) warning_expr = u'.*/warnings.txt:4: ' \ u'WARNING: Inline literal start-string without end-string.\n' - yield assert_re_search, warning_expr, warnings + assert_re_search(warning_expr, warnings) + +@sphinx_intl +@pytest.mark.sphinx('text') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_text_warning_node(app): + app.build() + # test warnings in translation result = (app.outdir / 'warnings.txt').text(encoding='utf-8') expect = (u"I18N WITH REST WARNINGS" u"\n***********************\n" u"\nLINE OF >>``<reference') - yield assert_equal, len(re.findall(expected_expr, result)), 2 + assert len(re.findall(expected_expr, result)) == 2 expected_expr = ('reference') - yield assert_equal, len(re.findall(expected_expr, result)), 0 + assert len(re.findall(expected_expr, result)) == 0 expected_expr = ('I18N WITH ' 'REFS INCONSISTENCY') - yield assert_equal, len(re.findall(expected_expr, result)), 1 + assert len(re.findall(expected_expr, result)) == 1 + +@sphinx_intl +@pytest.mark.sphinx('html') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_html_index_entries(app): + app.build() # --- index entries: regression test for #976 - result = (app.outdir / 'genindex.html').text(encoding='utf-8') def wrap(tag, keyword): @@ -440,10 +581,15 @@ def test_html_builder(app, status, warning): wrap('a', 'BUILTIN'), ] for expr in expected_exprs: - yield assert_re_search, expr, result, re.M + assert_re_search(expr, result, re.M) + +@sphinx_intl +@pytest.mark.sphinx('html') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_html_versionchanges(app): + app.build() # --- versionchanges - result = (app.outdir / 'versionchange.html').text(encoding='utf-8') def get_content(result, name): @@ -459,88 +605,108 @@ def test_html_builder(app, status, warning): u"""THIS IS THE FIRST PARAGRAPH OF DEPRECATED.

\n""" u"""

THIS IS THE SECOND PARAGRAPH OF DEPRECATED.

\n""") matched_content = get_content(result, "deprecated") - yield assert_equal, expect1, matched_content + assert expect1 == matched_content expect2 = ( u"""

New in version 1.0: """ u"""THIS IS THE FIRST PARAGRAPH OF VERSIONADDED.

\n""") matched_content = get_content(result, "versionadded") - yield assert_equal, expect2, matched_content + assert expect2 == matched_content expect3 = ( u"""

Changed in version 1.0: """ u"""THIS IS THE FIRST PARAGRAPH OF VERSIONCHANGED.

\n""") matched_content = get_content(result, "versionchanged") - yield assert_equal, expect3, matched_content + assert expect3 == matched_content + +@sphinx_intl +@pytest.mark.sphinx('html') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_html_docfields(app): + app.build() # --- docfields - # expect no error by build (app.outdir / 'docfields.html').text(encoding='utf-8') + +@sphinx_intl +@pytest.mark.sphinx('html') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_html_template(app): + app.build() # --- gettext template - result = (app.outdir / 'index.html').text(encoding='utf-8') - yield assert_in, "WELCOME", result - yield assert_in, "SPHINX 2013.120", result + assert "WELCOME" in result + assert "SPHINX 2013.120" in result + +@sphinx_intl +@pytest.mark.sphinx('html') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_html_rebuild_mo(app): + app.build() # --- rebuild by .mo mtime - app.builder.build_update() updated = app.env.update(app.config, app.srcdir, app.doctreedir, app) - yield assert_equal, len(updated), 0 + assert len(updated) == 0 (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime(None) updated = app.env.update(app.config, app.srcdir, app.doctreedir, app) - yield assert_equal, len(updated), 1 + assert len(updated) == 1 -@gen_with_intl_app('xml', freshenv=True) -def test_xml_builder(app, status, warning): - app.builder.build_all() - +@sphinx_intl +@pytest.mark.sphinx('xml') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_xml_footnotes(app, warning): + app.build() # --- footnotes: regression test for fix #955, #1176 - et = etree_parse(app.outdir / 'footnote.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') - yield (assert_elem, - para0[0], - ['I18N WITH FOOTNOTE', 'INCLUDE THIS CONTENTS', - '2', '[ref]', '1', '100', '.'], - ['i18n-with-footnote', 'ref']) + assert_elem( + para0[0], + ['I18N WITH FOOTNOTE', 'INCLUDE THIS CONTENTS', + '2', '[ref]', '1', '100', '.'], + ['i18n-with-footnote', 'ref']) footnote0 = secs[0].findall('footnote') - yield (assert_elem, - footnote0[0], - ['1', 'THIS IS A AUTO NUMBERED FOOTNOTE.'], - None, - ['1']) - yield (assert_elem, - footnote0[1], - ['100', 'THIS IS A NUMBERED FOOTNOTE.'], - None, - ['100']) - yield (assert_elem, - footnote0[2], - ['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'], - None, - ['named']) + assert_elem( + footnote0[0], + ['1', 'THIS IS A AUTO NUMBERED FOOTNOTE.'], + None, + ['1']) + assert_elem( + footnote0[1], + ['100', 'THIS IS A NUMBERED FOOTNOTE.'], + None, + ['100']) + assert_elem( + footnote0[2], + ['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'], + None, + ['named']) citation0 = secs[0].findall('citation') - yield (assert_elem, - citation0[0], - ['ref', 'THIS IS A NAMED FOOTNOTE.'], - None, - ['ref']) + assert_elem( + citation0[0], + ['ref', 'THIS IS A NAMED FOOTNOTE.'], + None, + ['ref']) warnings = getwarning(warning) warning_expr = u'.*/footnote.xml:\\d*: SEVERE: Duplicate ID: ".*".\n' - yield assert_not_re_search, warning_expr, warnings + assert_not_re_search(warning_expr, warnings) + +@sphinx_intl +@pytest.mark.sphinx('xml') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_xml_footnote_backlinks(app): + app.build() # --- footnote backlinks: i18n test for #1058 - et = etree_parse(app.outdir / 'footnote.xml') secs = et.findall('section') @@ -553,207 +719,234 @@ def test_xml_builder(app, status, warning): for footnote in footnote0: ids = footnote.attrib.get('ids') backrefs = footnote.attrib.get('backrefs') - yield assert_equal, refid2id[ids], backrefs + assert refid2id[ids] == backrefs + +@sphinx_intl +@pytest.mark.sphinx('xml') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_xml_refs_in_python_domain(app): + app.build() # --- refs in the Python domain - et = etree_parse(app.outdir / 'refs_python_domain.xml') secs = et.findall('section') # regression test for fix #1363 para0 = secs[0].findall('paragraph') - yield (assert_elem, - para0[0], - ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'], - ['sensitive.sensitive_variables']) + assert_elem( + para0[0], + ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'], + ['sensitive.sensitive_variables']) + +@sphinx_intl +@pytest.mark.sphinx('xml') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_xml_keep_external_links(app): + app.build() # --- keep external links: regression test for #1044 - et = etree_parse(app.outdir / 'external_links.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') # external link check - yield (assert_elem, - para0[0], - ['EXTERNAL LINK TO', 'Python', '.'], - ['http://python.org/index.html']) + assert_elem( + para0[0], + ['EXTERNAL LINK TO', 'Python', '.'], + ['http://python.org/index.html']) # internal link check - yield (assert_elem, - para0[1], - ['EXTERNAL LINKS', 'IS INTERNAL LINK.'], - ['i18n-with-external-links']) + assert_elem( + para0[1], + ['EXTERNAL LINKS', 'IS INTERNAL LINK.'], + ['i18n-with-external-links']) # inline link check - yield (assert_elem, - para0[2], - ['INLINE LINK BY', 'THE SPHINX SITE', '.'], - ['http://sphinx-doc.org']) + assert_elem( + para0[2], + ['INLINE LINK BY', 'THE SPHINX SITE', '.'], + ['http://sphinx-doc.org']) # unnamed link check - yield (assert_elem, - para0[3], - ['UNNAMED', 'LINK', '.'], - ['http://google.com']) + assert_elem( + para0[3], + ['UNNAMED', 'LINK', '.'], + ['http://google.com']) # link target swapped translation para1 = secs[1].findall('paragraph') - yield (assert_elem, - para1[0], - ['LINK TO', 'external2', 'AND', 'external1', '.'], - ['http://example.com/external2', - 'http://example.com/external1']) - yield (assert_elem, + assert_elem( + para1[0], + ['LINK TO', 'external2', 'AND', 'external1', '.'], + ['http://example.com/external2', + 'http://example.com/external1']) + assert_elem( para1[1], ['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'], ['http://python.org', 'http://sphinx-doc.org']) # multiple references in the same line para2 = secs[2].findall('paragraph') - yield (assert_elem, - para2[0], - ['LINK TO', 'EXTERNAL LINKS', ',', 'Python', ',', - 'THE SPHINX SITE', ',', 'UNNAMED', 'AND', - 'THE PYTHON SITE', '.'], - ['i18n-with-external-links', 'http://python.org/index.html', - 'http://sphinx-doc.org', 'http://google.com', - 'http://python.org']) + assert_elem( + para2[0], + ['LINK TO', 'EXTERNAL LINKS', ',', 'Python', ',', + 'THE SPHINX SITE', ',', 'UNNAMED', 'AND', + 'THE PYTHON SITE', '.'], + ['i18n-with-external-links', 'http://python.org/index.html', + 'http://sphinx-doc.org', 'http://google.com', + 'http://python.org']) + +@sphinx_intl +@pytest.mark.sphinx('xml') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_xml_role_xref(app): + app.build() # --- role xref: regression test for #1090, #1193 - et = etree_parse(app.outdir / 'role_xref.xml') sec1, sec2 = et.findall('section') para1, = sec1.findall('paragraph') - yield (assert_elem, - para1, - ['LINK TO', "I18N ROCK'N ROLE XREF", ',', 'CONTENTS', ',', - 'SOME NEW TERM', '.'], - ['i18n-role-xref', 'contents', - 'glossary_terms#term-some-term']) + assert_elem( + para1, + ['LINK TO', "I18N ROCK'N ROLE XREF", ',', 'CONTENTS', ',', + 'SOME NEW TERM', '.'], + ['i18n-role-xref', 'contents', + 'glossary_terms#term-some-term']) para2 = sec2.findall('paragraph') - yield (assert_elem, - para2[0], - ['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'], - ['glossary_terms#term-some-other-term', - 'glossary_terms#term-some-term']) - yield(assert_elem, - para2[1], - ['LINK TO', 'SAME TYPE LINKS', 'AND', - "I18N ROCK'N ROLE XREF", '.'], - ['same-type-links', 'i18n-role-xref']) - yield (assert_elem, - para2[2], - ['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'], - ['glossary_terms', 'contents']) - yield (assert_elem, - para2[3], - ['LINK TO', '--module', 'AND', '-m', '.'], - ['cmdoption-module', 'cmdoption-m']) - yield (assert_elem, - para2[4], - ['LINK TO', 'env2', 'AND', 'env1', '.'], - ['envvar-env2', 'envvar-env1']) - yield (assert_elem, - para2[5], - ['LINK TO', 'token2', 'AND', 'token1', '.'], - []) # TODO: how do I link token role to productionlist? - yield (assert_elem, - para2[6], - ['LINK TO', 'same-type-links', 'AND', "i18n-role-xref", '.'], - ['same-type-links', 'i18n-role-xref']) + assert_elem( + para2[0], + ['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'], + ['glossary_terms#term-some-other-term', + 'glossary_terms#term-some-term']) + assert_elem( + para2[1], + ['LINK TO', 'SAME TYPE LINKS', 'AND', + "I18N ROCK'N ROLE XREF", '.'], + ['same-type-links', 'i18n-role-xref']) + assert_elem( + para2[2], + ['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'], + ['glossary_terms', 'contents']) + assert_elem( + para2[3], + ['LINK TO', '--module', 'AND', '-m', '.'], + ['cmdoption-module', 'cmdoption-m']) + assert_elem( + para2[4], + ['LINK TO', 'env2', 'AND', 'env1', '.'], + ['envvar-env2', 'envvar-env1']) + assert_elem( + para2[5], + ['LINK TO', 'token2', 'AND', 'token1', '.'], + []) # TODO: how do I link token role to productionlist? + assert_elem( + para2[6], + ['LINK TO', 'same-type-links', 'AND', "i18n-role-xref", '.'], + ['same-type-links', 'i18n-role-xref']) + +@sphinx_intl +@pytest.mark.sphinx('xml') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_xml_warnings(app, warning): + app.build() # warnings warnings = getwarning(warning) - yield assert_not_in, 'term not in glossary', warnings - yield assert_not_in, 'undefined label', warnings - yield assert_not_in, 'unknown document', warnings + assert 'term not in glossary' not in warnings + assert 'undefined label' not in warnings + assert 'unknown document' not in warnings + +@sphinx_intl +@pytest.mark.sphinx('xml') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_xml_label_targets(app): + app.build() # --- label targets: regression test for #1193, #1265 - et = etree_parse(app.outdir / 'label_target.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') - yield (assert_elem, - para0[0], - ['X SECTION AND LABEL', 'POINT TO', 'implicit-target', 'AND', - 'X SECTION AND LABEL', 'POINT TO', 'section-and-label', '.'], - ['implicit-target', 'section-and-label']) + assert_elem( + para0[0], + ['X SECTION AND LABEL', 'POINT TO', 'implicit-target', 'AND', + 'X SECTION AND LABEL', 'POINT TO', 'section-and-label', '.'], + ['implicit-target', 'section-and-label']) para1 = secs[1].findall('paragraph') - yield (assert_elem, - para1[0], - ['X EXPLICIT-TARGET', 'POINT TO', 'explicit-target', 'AND', - 'X EXPLICIT-TARGET', 'POINT TO DUPLICATED ID LIKE', 'id1', - '.'], - ['explicit-target', 'id1']) + assert_elem( + para1[0], + ['X EXPLICIT-TARGET', 'POINT TO', 'explicit-target', 'AND', + 'X EXPLICIT-TARGET', 'POINT TO DUPLICATED ID LIKE', 'id1', + '.'], + ['explicit-target', 'id1']) para2 = secs[2].findall('paragraph') - yield (assert_elem, - para2[0], - ['X IMPLICIT SECTION NAME', 'POINT TO', - 'implicit-section-name', '.'], - ['implicit-section-name']) + assert_elem( + para2[0], + ['X IMPLICIT SECTION NAME', 'POINT TO', + 'implicit-section-name', '.'], + ['implicit-section-name']) sec2 = secs[2].findall('section') para2_0 = sec2[0].findall('paragraph') - yield (assert_elem, - para2_0[0], - ['`X DUPLICATED SUB SECTION`_', 'IS BROKEN LINK.'], - []) + assert_elem( + para2_0[0], + ['`X DUPLICATED SUB SECTION`_', 'IS BROKEN LINK.'], + []) para3 = secs[3].findall('paragraph') - yield (assert_elem, - para3[0], - ['X', 'bridge label', - 'IS NOT TRANSLATABLE BUT LINKED TO TRANSLATED ' + - 'SECTION TITLE.'], - ['label-bridged-target-section']) - yield (assert_elem, - para3[1], - ['X', 'bridge label', 'POINT TO', - 'LABEL BRIDGED TARGET SECTION', 'AND', 'bridge label2', - 'POINT TO', 'SECTION AND LABEL', '. THE SECOND APPEARED', - 'bridge label2', 'POINT TO CORRECT TARGET.'], - ['label-bridged-target-section', - 'section-and-label', - 'section-and-label']) + assert_elem( + para3[0], + ['X', 'bridge label', + 'IS NOT TRANSLATABLE BUT LINKED TO TRANSLATED ' + + 'SECTION TITLE.'], + ['label-bridged-target-section']) + assert_elem( + para3[1], + ['X', 'bridge label', 'POINT TO', + 'LABEL BRIDGED TARGET SECTION', 'AND', 'bridge label2', + 'POINT TO', 'SECTION AND LABEL', '. THE SECOND APPEARED', + 'bridge label2', 'POINT TO CORRECT TARGET.'], + ['label-bridged-target-section', + 'section-and-label', + 'section-and-label']) -@gen_with_intl_app('html', freshenv=True) -def test_additional_targets_should_not_be_translated(app, status, warning): - app.builder.build_all() - +@sphinx_intl +@pytest.mark.sphinx('html') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_additional_targets_should_not_be_translated(app): + app.build() # [literalblock.txt] result = (app.outdir / 'literalblock.html').text(encoding='utf-8') # title should be translated expected_expr = 'CODE-BLOCKS' - yield assert_count(expected_expr, result, 2) + assert_count(expected_expr, result, 2) # ruby code block should not be translated but be highlighted expected_expr = """'result'""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # C code block without lang should not be translated and *ruby* highlighted expected_expr = """#include <stdlib.h>""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # C code block with lang should not be translated but be *C* highlighted expected_expr = ("""#include """ """<stdio.h>""") - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # doctest block should not be translated but be highlighted expected_expr = ( """>>> """ """import sys """ """# sys importing""") - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # [raw.txt] @@ -761,7 +954,7 @@ def test_additional_targets_should_not_be_translated(app, status, warning): # raw block should not be translated expected_expr = """""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # [figure.txt] @@ -769,52 +962,54 @@ def test_additional_targets_should_not_be_translated(app, status, warning): # alt and src for image block should not be translated expected_expr = """i18n""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # alt and src for figure block should not be translated expected_expr = """img""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) -@gen_with_intl_app('html', freshenv=True, - confoverrides={ - 'gettext_additional_targets': [ - 'index', - 'literal-block', - 'doctest-block', - 'raw', - 'image', - ], - }) -def test_additional_targets_should_be_translated(app, status, warning): - app.builder.build_all() - +@sphinx_intl +@pytest.mark.sphinx('html', confoverrides={ + 'language': 'xx', 'locale_dirs': ['.'], + 'gettext_compact': False, + 'gettext_additional_targets': [ + 'index', + 'literal-block', + 'doctest-block', + 'raw', + 'image', + ], +}) +@pytest.mark.test_params(specific_srcdir=True) +def test_additional_targets_should_be_translated(app): + app.build() # [literalblock.txt] result = (app.outdir / 'literalblock.html').text(encoding='utf-8') # title should be translated expected_expr = 'CODE-BLOCKS' - yield assert_count(expected_expr, result, 2) + assert_count(expected_expr, result, 2) # ruby code block should be translated and be highlighted expected_expr = """'RESULT'""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # C code block without lang should be translated and *ruby* highlighted expected_expr = """#include <STDLIB.H>""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # C code block with lang should be translated and be *C* highlighted expected_expr = ("""#include """ """<STDIO.H>""") - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # doctest block should not be translated but be highlighted expected_expr = ( """>>> """ """import sys """ """# SYS IMPORTING""") - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # [raw.txt] @@ -822,7 +1017,7 @@ def test_additional_targets_should_be_translated(app, status, warning): # raw block should be translated expected_expr = """""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # [figure.txt] @@ -830,26 +1025,31 @@ def test_additional_targets_should_be_translated(app, status, warning): # alt and src for image block should be translated expected_expr = """I18N -> IMG""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) # alt and src for figure block should be translated expected_expr = """IMG -> I18N""" - yield assert_count(expected_expr, result, 1) + assert_count(expected_expr, result, 1) -@gen_with_intl_app('text', freshenv=True) -def test_references(app, status, warning): +@sphinx_intl +@pytest.mark.sphinx('text') +@pytest.mark.test_params(shared_result='test_intl_basic') +def test_text_references(app, warning): app.builder.build_specific([app.srcdir / 'refs.txt']) warnings = warning.getvalue().replace(os.sep, '/') warning_expr = u'refs.txt:\\d+: ERROR: Unknown target name:' - yield assert_count(warning_expr, warnings, 0) + assert_count(warning_expr, warnings, 0) -@pytest.mark.sphinx('dummy', testroot='image-glob', confoverrides={'language': 'xx'}) -def test_image_glob_intl(app, status, warning): - app.builder.build_all() - +@pytest.mark.sphinx( + 'dummy', testroot='image-glob', + confoverrides={'language': 'xx'} +) +@pytest.mark.test_params(specific_srcdir='test_intl_image_glob') +def test_image_glob_intl(app): + app.build() # index.rst doctree = pickle.loads((app.doctreedir / 'index.doctree').bytes()) @@ -887,12 +1087,16 @@ def test_image_glob_intl(app, status, warning): 'image/svg+xml': 'subdir/svgimg.xx.svg'}) -@pytest.mark.sphinx('dummy', testroot='image-glob', - confoverrides={'language': 'xx', - 'figure_language_filename': u'{root}{ext}.{language}'}) -def test_image_glob_intl_using_figure_language_filename(app, status, warning): - app.builder.build_all() - +@pytest.mark.sphinx( + 'dummy', testroot='image-glob', + confoverrides={ + 'language': 'xx', + 'figure_language_filename': u'{root}{ext}.{language}', + } +) +@pytest.mark.test_params(specific_srcdir='test_intl_image_glob') +def test_image_glob_intl_using_figure_language_filename(app): + app.build() # index.rst doctree = pickle.loads((app.doctreedir / 'index.doctree').bytes()) diff --git a/tests/test_markup.py b/tests/test_markup.py index 9bfb44e25..3e190a4d6 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -13,7 +13,7 @@ import re import pickle from docutils import frontend, utils, nodes -from docutils.parsers import rst +from docutils.parsers.rst import Parser as RstParser from sphinx import addnodes from sphinx.util import texescape @@ -22,31 +22,37 @@ from sphinx.writers.html import HTMLWriter, SmartyPantsHTMLTranslator from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator import pytest -from util import TestApp, assert_node +from util import assert_node -app = settings = parser = domain_context = None - - -def setup_module(): - global app, settings, parser, domain_context +@pytest.fixture +def settings(app): texescape.init() # otherwise done by the latex builder - app = TestApp() optparser = frontend.OptionParser( - components=(rst.Parser, HTMLWriter, LaTeXWriter)) + components=(RstParser, HTMLWriter, LaTeXWriter)) settings = optparser.get_default_values() settings.env = app.builder.env settings.env.temp_data['docname'] = 'dummy' - parser = rst.Parser() domain_context = sphinx_domains(settings.env) domain_context.enable() - - -def teardown_module(): - app.cleanup() + yield settings domain_context.disable() +@pytest.fixture +def parse(settings): + def parse_(rst): + document = utils.new_document(b'test data', settings) + document['file'] = 'dummy' + parser = RstParser() + parser.parse(rst, document) + for msg in document.traverse(nodes.system_message): + if msg['level'] == 1: + msg.replace_self([]) + return document + return parse_ + + # since we're not resolving the markup afterwards, these nodes may remain class ForgivingTranslator: def visit_pending_xref(self, node): @@ -64,93 +70,158 @@ class ForgivingLaTeXTranslator(LaTeXTranslator, ForgivingTranslator): pass -def verify_re(rst, html_expected, latex_expected): - document = utils.new_document(b'test data', settings) - document['file'] = 'dummy' - parser.parse(rst, document) - for msg in document.traverse(nodes.system_message): - if msg['level'] == 1: - msg.replace_self([]) - - if html_expected: +@pytest.fixture +def verify_re_html(app, parse): + def verify(rst, html_expected): + document = parse(rst) html_translator = ForgivingHTMLTranslator(app.builder, document) document.walkabout(html_translator) html_translated = ''.join(html_translator.fragment).strip() assert re.match(html_expected, html_translated), 'from ' + rst + return verify - if latex_expected: + +@pytest.fixture +def verify_re_latex(app, parse): + def verify(rst, latex_expected): + document = parse(rst) latex_translator = ForgivingLaTeXTranslator(document, app.builder) latex_translator.first_document = -1 # don't write \begin{document} document.walkabout(latex_translator) latex_translated = ''.join(latex_translator.body).strip() assert re.match(latex_expected, latex_translated), 'from ' + repr(rst) + return verify -def verify(rst, html_expected, latex_expected): - if html_expected: - html_expected = re.escape(html_expected) + '$' - if latex_expected: - latex_expected = re.escape(latex_expected) + '$' - verify_re(rst, html_expected, latex_expected) +@pytest.fixture +def verify_re(verify_re_html, verify_re_latex): + def verify_re_(rst, html_expected, latex_expected): + if html_expected: + return verify_re_html(rst, html_expected) + if latex_expected: + return verify_re_latex(rst, latex_expected) + return verify_re_ -def test_inline(): - # correct interpretation of code with whitespace - _html = ('

' - 'code   sample

') - yield verify_re, '``code sample``', _html, r'\\sphinxcode{code sample}' - yield verify_re, ':samp:`code sample`', _html, r'\\sphinxcode{code sample}' - - # interpolation of braces in samp and file roles (HTML only) - yield (verify, ':samp:`a{b}c`', - '

a' - 'b' - 'c

', - '\\sphinxcode{a\\sphinxstyleemphasis{b}c}') - - # interpolation of arrows in menuselection - yield (verify, ':menuselection:`a --> b`', - u'

a \N{TRIANGULAR BULLET} b

', - '\\sphinxmenuselection{a \\(\\rightarrow\\) b}') - - # interpolation of ampersands in guilabel/menuselection - yield (verify, ':guilabel:`&Foo -&&- &Bar`', - u'

Foo ' - '-&- Bar

', - r'\sphinxmenuselection{\sphinxaccelerator{F}oo -\&- \sphinxaccelerator{B}ar}') - - # non-interpolation of dashes in option role - yield (verify_re, ':option:`--with-option`', - '

' - '--with-option

$', - r'\\sphinxcode{-{-}with-option}$') - - # verify smarty-pants quotes - yield verify, '"John"', '

“John”

', "``John''" - # ... but not in literal text - yield (verify, '``"John"``', - '

' - '"John"

', - '\\sphinxcode{"John"}') - - # verify classes for inline roles - yield (verify, ':manpage:`mp(1)`', - '

mp(1)

', - '\\sphinxstyleliteralemphasis{mp(1)}') +@pytest.fixture +def verify(verify_re_html, verify_re_latex): + def verify_(rst, html_expected, latex_expected): + if html_expected: + return verify_re_html(rst, re.escape(html_expected) + '$') + if latex_expected: + return verify_re_latex(rst, re.escape(latex_expected) + '$') + return verify_ -def test_latex_escaping(): - # correct escaping in normal mode - yield (verify, u'Γ\\\\∞$', None, - r'\(\Gamma\)\textbackslash{}\(\infty\)\$') - # in verbatim code fragments - yield (verify, u'::\n\n @Γ\\∞${}', None, - u'\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]\n' - u'@\\(\\Gamma\\)\\PYGZbs{}\\(\\infty\\)\\PYGZdl{}\\PYGZob{}\\PYGZcb{}\n' - u'\\end{sphinxVerbatim}') - # in URIs - yield (verify_re, u'`test `_', None, - r'\\href{http://example.com/~me/}{test}.*') +@pytest.fixture +def get_verifier(verify, verify_re): + v = { + 'verify': verify, + 'verify_re': verify_re, + } + def get(name): + return v[name] + return get + + +@pytest.mark.parametrize('type,rst,html_expected,latex_expected', [ + ( + # correct interpretation of code with whitespace + 'verify_re', + '``code sample``', + ('

' + 'code   sample

'), + r'\\sphinxcode{code sample}', + ), + ( + # correct interpretation of code with whitespace + 'verify_re', + ':samp:`code sample`', + ('

' + 'code   sample

'), + r'\\sphinxcode{code sample}', + ), + ( + # interpolation of braces in samp and file roles (HTML only) + 'verify', + ':samp:`a{b}c`', + ('

a' + 'b' + 'c

'), + '\\sphinxcode{a\\sphinxstyleemphasis{b}c}', + ), + ( + # interpolation of arrows in menuselection + 'verify', + ':menuselection:`a --> b`', + (u'

a \N{TRIANGULAR BULLET} b

'), + '\\sphinxmenuselection{a \\(\\rightarrow\\) b}', + ), + ( + # interpolation of ampersands in guilabel/menuselection + 'verify', + ':guilabel:`&Foo -&&- &Bar`', + (u'

Foo ' + '-&- Bar

'), + r'\sphinxmenuselection{\sphinxaccelerator{F}oo -\&- \sphinxaccelerator{B}ar}', + ), + ( + # non-interpolation of dashes in option role + 'verify_re', + ':option:`--with-option`', + ('

' + '--with-option

$'), + r'\\sphinxcode{-{-}with-option}$', + ), + ( + # verify smarty-pants quotes + 'verify', + '"John"', + '

“John”

', + "``John''", + ), + ( + # ... but not in literal text + 'verify', + '``"John"``', + ('

' + '"John"

'), + '\\sphinxcode{"John"}', + ), + ( + # verify classes for inline roles + 'verify', + ':manpage:`mp(1)`', + '

mp(1)

', + '\\sphinxstyleliteralemphasis{mp(1)}', + ), + ( + # correct escaping in normal mode + 'verify', + u'Γ\\\\∞$', + None, + r'\(\Gamma\)\textbackslash{}\(\infty\)\$', + ), + ( + # in verbatim code fragments + 'verify', + u'::\n\n @Γ\\∞${}', + None, + (u'\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]\n' + u'@\\(\\Gamma\\)\\PYGZbs{}\\(\\infty\\)\\PYGZdl{}\\PYGZob{}\\PYGZcb{}\n' + u'\\end{sphinxVerbatim}'), + ), + ( + # in URIs + 'verify_re', + u'`test `_', + None, + r'\\href{http://example.com/~me/}{test}.*', + ), +]) +def test_inline(get_verifier, type, rst, html_expected, latex_expected): + verifier = get_verifier(type) + verifier(rst, html_expected, latex_expected) @pytest.mark.sphinx('dummy', testroot='prolog') diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 3fbdd8830..d7665e58e 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -12,30 +12,27 @@ from __future__ import print_function import os import datetime -from os import path +import pytest from babel.messages.mofile import read_mo from sphinx.util import i18n from sphinx.errors import SphinxError -import pytest - -from util import TestApp def test_catalog_info_for_file_and_path(): cat = i18n.CatalogInfo('path', 'domain', 'utf-8') assert cat.po_file == 'domain.po' assert cat.mo_file == 'domain.mo' - assert cat.po_path == path.join('path', 'domain.po') - assert cat.mo_path == path.join('path', 'domain.mo') + assert cat.po_path == os.path.join('path', 'domain.po') + assert cat.mo_path == os.path.join('path', 'domain.mo') def test_catalog_info_for_sub_domain_file_and_path(): cat = i18n.CatalogInfo('path', 'sub/domain', 'utf-8') assert cat.po_file == 'sub/domain.po' assert cat.mo_file == 'sub/domain.mo' - assert cat.po_path == path.join('path', 'sub/domain.po') - assert cat.mo_path == path.join('path', 'sub/domain.mo') + assert cat.po_path == os.path.join('path', 'sub/domain.po') + assert cat.mo_path == os.path.join('path', 'sub/domain.mo') def test_catalog_outdated(tempdir): @@ -55,7 +52,7 @@ def test_catalog_write_mo(tempdir): (tempdir / 'test.po').write_text('#') cat = i18n.CatalogInfo(tempdir, 'test', 'utf-8') cat.write_mo('en') - assert path.exists(cat.mo_path) + assert os.path.exists(cat.mo_path) with open(cat.mo_path, 'rb') as f: assert read_mo(f) is not None @@ -207,9 +204,7 @@ def test_format_date(): assert i18n.format_date(format, date=date) == 'Feb 7, 2016' -def test_get_filename_for_language(): - app = TestApp() - +def test_get_filename_for_language(app): # language is None app.env.config.language = None assert app.env.config.language is None diff --git a/tests/test_util_nodes.py b/tests/test_util_nodes.py index ac8c3645d..7aded8a4b 100644 --- a/tests/test_util_nodes.py +++ b/tests/test_util_nodes.py @@ -17,6 +17,7 @@ from docutils import frontend from sphinx.util.nodes import extract_messages, clean_astext from sphinx.transforms import ApplySourceWorkaround +import pytest def _transform(doctree): @@ -49,84 +50,63 @@ def assert_node_count(messages, node_type, expect_count): % (node_type, node_list, count, expect_count)) -def test_extract_messages(): - text = dedent( - """ - .. admonition:: admonition title +@pytest.mark.parametrize( + 'rst,node_cls,count', + [ + ( + """ + .. admonition:: admonition title - admonition body - """ - ) - yield ( - assert_node_count, - extract_messages(_get_doctree(text)), - nodes.title, 1, - ) + admonition body + """, + nodes.title, 1 + ), + ( + """ + .. figure:: foo.jpg - text = dedent( - """ - .. figure:: foo.jpg + this is title + """, + nodes.caption, 1, + ), + ( + """ + .. rubric:: spam + """, + nodes.rubric, 1, + ), + ( + """ + | spam + | egg + """, + nodes.line, 2, + ), + ( + """ + section + ======= - this is title - """ - ) - yield ( - assert_node_count, - extract_messages(_get_doctree(text)), - nodes.caption, 1, - ) + +----------------+ + | | **Title 1** | + | | Message 1 | + +----------------+ + """, + nodes.line, 2, + ), + ( + """ + * | **Title 1** + | Message 1 + """, + nodes.line, 2, - text = dedent( - """ - .. rubric:: spam - """ - ) - yield ( - assert_node_count, - extract_messages(_get_doctree(text)), - nodes.rubric, 1, - ) - - text = dedent( - """ - | spam - | egg - """ - ) - yield ( - assert_node_count, - extract_messages(_get_doctree(text)), - nodes.line, 2, - ) - - text = dedent( - """ - section - ======= - - +----------------+ - | | **Title 1** | - | | Message 1 | - +----------------+ - """ - ) - yield ( - assert_node_count, - extract_messages(_get_doctree(text)), - nodes.line, 2, - ) - - text = dedent( - """ - * | **Title 1** - | Message 1 - """ - ) - yield ( - assert_node_count, - extract_messages(_get_doctree(text)), - nodes.line, 2, - ) + ), + ] +) +def test_extract_messages(rst, node_cls, count): + msg = extract_messages(_get_doctree(dedent(rst))) + assert_node_count(msg, node_cls, count) def test_extract_messages_without_rawsource(): diff --git a/tests/test_versioning.py b/tests/test_versioning.py index f5b5057d7..4b9ebefcb 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -16,7 +16,7 @@ from docutils.parsers.rst.directives.html import MetaBody from sphinx import addnodes from sphinx.versioning import add_uids, merge_doctrees, get_ratio -from util import TestApp +from util import SphinxTestApp app = original = original_uids = None @@ -24,7 +24,7 @@ app = original = original_uids = None def setup_module(): global app, original, original_uids - app = TestApp(testroot='versioning') + app = SphinxTestApp(testroot='versioning') app.builder.env.app = app app.connect('doctree-resolved', on_doctree_resolved) app.build() diff --git a/tests/test_websupport.py b/tests/test_websupport.py index 3592dd5ec..bded050d3 100644 --- a/tests/test_websupport.py +++ b/tests/test_websupport.py @@ -23,7 +23,7 @@ except ImportError: sqlalchemy_missing = True import pytest -from util import rootdir, tempdir, skip_if +from util import rootdir, tempdir @pytest.fixture @@ -57,13 +57,13 @@ def test_no_srcdir(support): support.build() -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_build(support): support.build() -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_get_document(support): with pytest.raises(DocumentNotFoundError): @@ -74,7 +74,7 @@ def test_get_document(support): and contents['sidebar'] and contents['relbar'] -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_comments(support): session = Session() @@ -123,7 +123,7 @@ def test_comments(support): assert children[0]['text'] == '

Child test comment

\n' -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_user_delete_comments(support): def get_comment(): @@ -152,7 +152,7 @@ def moderation_callback(comment): called = True -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support(moderation_callback=moderation_callback) def test_moderation(support): session = Session() @@ -178,7 +178,7 @@ def test_moderation(support): assert len(comments) == 1 -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_moderator_delete_comments(support): def get_comment(): @@ -194,7 +194,7 @@ def test_moderator_delete_comments(support): get_comment() -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_update_username(support): support.update_username('user_two', 'new_user_two') @@ -213,7 +213,7 @@ def test_update_username(support): assert len(votes) == 0 -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_proposals(support): session = Session() @@ -229,7 +229,7 @@ def test_proposals(support): proposal=proposal) -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@pytest.mark.skipif(sqlalchemy_missing, reason='needs sqlalchemy') @with_support() def test_voting(support): session = Session() diff --git a/tests/util.py b/tests/util.py index 8f2d1ff9a..a756b6006 100644 --- a/tests/util.py +++ b/tests/util.py @@ -207,7 +207,6 @@ def strip_escseq(text): # ############################################# # DEPRECATED implementations -import tempfile from six import StringIO From dc02f2535d7e92dfd1c2f7b9839e640ef499880c Mon Sep 17 00:00:00 2001 From: jfbu Date: Sun, 8 Jan 2017 11:50:50 +0100 Subject: [PATCH 2/6] Fix #3315: Bibliography crashes on latex build with docclass 'memoir' --- sphinx/texinputs/sphinx.sty | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index f57283c19..8d43661ba 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -1090,8 +1090,14 @@ % make commands known to non-Sphinx document classes \providecommand*{\sphinxtableofcontents}{\tableofcontents} -\providecommand*{\sphinxthebibliography}{\thebibliography} -\providecommand*{\sphinxtheindex}{\theindex} +\spx@ifundefined{sphinxthebibliography} + {\newenvironment + {sphinxthebibliography}{\begin{thebibliography}}{\end{thebibliography}}% + } + {}% else clause of ifundefined +\spx@ifundefined{sphinxtheindex} + {\newenvironment{sphinxtheindex}{\begin{theindex}}{\end{theindex}}}% + {}% else clause of ifundefined % remove LaTeX's cap on nesting depth if 'maxlistdepth' key used. % This is a hack, which works with the standard classes: it assumes \@toodeep From b5530629a99d3e2b1060b9329a3134e0e6ebb10b Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Mon, 9 Jan 2017 17:41:47 +0900 Subject: [PATCH 3/6] pytest: update by reviewing * remove 'specific_srcdir' feature * simplify 'shared_result' feature * rename 'AppWrapper' into 'SphinxTestAppWrapperForSkipBuilding' --- tests/conftest.py | 41 +++++++++++++--------------------------- tests/test_build_html.py | 18 ++++++++++++------ tests/test_intl.py | 31 ++++++++++++++++-------------- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8032e8e6c..6fab88b12 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,10 +35,11 @@ def app_params(request, test_params, shared_result): # ##### process pytest.mark.test_params - if test_params['specific_srcdir'] and 'srcdir' not in kwargs: - kwargs['srcdir'] = test_params['specific_srcdir'] - if test_params['shared_result']: + if 'srcdir' in kwargs: + raise pytest.Exception('You can not spcify shared_result and ' + 'srcdir in same time.') + kwargs['srcdir'] = test_params['shared_result'] restore = shared_result.restore(test_params['shared_result']) kwargs.update(restore) @@ -66,43 +67,27 @@ def test_params(request): """ test parameters that is specified by 'pytest.mark.test_params' - :param Union[str, bool, None] specific_srcdir: - If True, testroot directory will be copied into - '/'. - If string is specified, it copied into '/'. - You can used this feature for providing special crafted source - directory. Also you can used for sharing source directory for - parametrized testing and/or inter test functions. Default is None. - :param Union[str, bool, None] shared_result: - If True, app._status and app._warning objects will be shared in the - parametrized test functions. If string is specified, the objects will - be shred in the test functions that have same 'shared_result' value. - If you don't specify specific_srcdir, this option override - specific_srcdir param by 'shared_result' value. Default is None. + :param Union[str] shared_result: + If the value is provided, app._status and app._warning objects will be + shared in the parametrized test functions and/or test functions that + have same 'shared_result' value. + **NOTE**: You can not specify shared_result and srcdir in same time. """ env = request.node.get_marker('test_params') kwargs = env.kwargs if env else {} result = { - 'specific_srcdir': None, 'shared_result': None, } result.update(kwargs) if (result['shared_result'] and not isinstance(result['shared_result'], string_types)): - result['shared_result'] = request.node.originalname or request.node.name - - if result['shared_result'] and not result['specific_srcdir']: - result['specific_srcdir'] = result['shared_result'] - - if (result['specific_srcdir'] and - not isinstance(result['specific_srcdir'], string_types)): - result['specific_srcdir'] = request.node.originalname or request.node.name - + raise pytest.Exception('You can only provide a string type of value ' + 'for "shared_result" ') return result -class AppWrapper(object): +class SphinxTestAppWrapperForSkipBuilding(object): """ This class is a wrapper for SphinxTestApp to speed up the test by skipping `app.build` process if it is already built and there is even one output @@ -175,7 +160,7 @@ def make_app(test_params): app_ = util.SphinxTestApp(*args, **kwargs) apps.append(app_) if test_params['shared_result']: - app_ = AppWrapper(app_) + app_ = SphinxTestAppWrapperForSkipBuilding(app_) return app_ yield make diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 4a4eac2b6..63c8111e1 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -570,8 +570,10 @@ def test_numfig_disabled(app, cached_etree_parse, fname, expect): check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) -@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) -@pytest.mark.test_params(specific_srcdir=True) +@pytest.mark.sphinx( + 'html', testroot='numfig', + srcdir='test_numfig_without_numbered_toctree_warn', + confoverrides={'numfig': True}) def test_numfig_without_numbered_toctree_warn(app, warning): app.build() # remove :numbered: option @@ -667,8 +669,10 @@ def test_numfig_without_numbered_toctree_warn(app, warning): "span[@class='caption-number']", '^Listing 6 $', True), ], })) -@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) -@pytest.mark.test_params(specific_srcdir=True) +@pytest.mark.sphinx( + 'html', testroot='numfig', + srcdir='test_numfig_without_numbered_toctree', + confoverrides={'numfig': True}) def test_numfig_without_numbered_toctree(app, cached_etree_parse, fname, expect): # remove :numbered: option index = (app.srcdir / 'index.rst').text() @@ -1085,8 +1089,10 @@ def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect): (".//li/a/span", 'No.2', True), ], })) -@pytest.mark.sphinx('html', testroot='add_enumerable_node') -@pytest.mark.test_params(specific_srcdir=True) +@pytest.mark.sphinx( + 'html', testroot='add_enumerable_node', + srcdir='test_enumerable_node', +) def test_enumerable_node(app, cached_etree_parse, fname, expect): app.build() check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) diff --git a/tests/test_intl.py b/tests/test_intl.py index 1372c80d8..e7bb130ff 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -970,18 +970,21 @@ def test_additional_targets_should_not_be_translated(app): @sphinx_intl -@pytest.mark.sphinx('html', confoverrides={ - 'language': 'xx', 'locale_dirs': ['.'], - 'gettext_compact': False, - 'gettext_additional_targets': [ - 'index', - 'literal-block', - 'doctest-block', - 'raw', - 'image', - ], -}) -@pytest.mark.test_params(specific_srcdir=True) +@pytest.mark.sphinx( + 'html', + srcdir='test_additional_targets_should_be_translated', + confoverrides={ + 'language': 'xx', 'locale_dirs': ['.'], + 'gettext_compact': False, + 'gettext_additional_targets': [ + 'index', + 'literal-block', + 'doctest-block', + 'raw', + 'image', + ], + } +) def test_additional_targets_should_be_translated(app): app.build() # [literalblock.txt] @@ -1045,9 +1048,9 @@ def test_text_references(app, warning): @pytest.mark.sphinx( 'dummy', testroot='image-glob', + srcdir='test_intl_image_glob', confoverrides={'language': 'xx'} ) -@pytest.mark.test_params(specific_srcdir='test_intl_image_glob') def test_image_glob_intl(app): app.build() # index.rst @@ -1089,12 +1092,12 @@ def test_image_glob_intl(app): @pytest.mark.sphinx( 'dummy', testroot='image-glob', + srcdir='test_intl_image_glob', confoverrides={ 'language': 'xx', 'figure_language_filename': u'{root}{ext}.{language}', } ) -@pytest.mark.test_params(specific_srcdir='test_intl_image_glob') def test_image_glob_intl_using_figure_language_filename(app): app.build() # index.rst From d7d8786d894cc1bcbe349b464b21c988044a2d77 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 9 Jan 2017 18:39:08 +0900 Subject: [PATCH 4/6] Remove restriction for jinja2 (refs: #3314) --- test-reqs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-reqs.txt b/test-reqs.txt index 247b22b73..1877886c1 100644 --- a/test-reqs.txt +++ b/test-reqs.txt @@ -3,7 +3,7 @@ pytest>=3.0 pytest-cov mock six>=1.4 -Jinja2>=2.3,<2.9 +Jinja2>=2.3 Pygments>=2.0 docutils>=0.11 snowballstemmer>=1.1 From 2382377a6b3c8e8f47c66c6d750dc8e53a542603 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 9 Jan 2017 22:39:42 +0900 Subject: [PATCH 5/6] Fix typo --- sphinx/domains/std.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 021d26a46..55d0ec1ef 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -673,13 +673,13 @@ class StandardDomain(Domain): else: title = env.config.numfig_format.get(figtype, '') - if figname is None and '%{name}' in title: + if figname is None and '{name}' in title: env.warn_node('the link has no caption: %s' % title, node) return contnode else: fignum = '.'.join(map(str, fignumber)) if '{name}' in title or 'number' in title: - # new style format (cf. "Fig.%{number}") + # new style format (cf. "Fig.{number}") if figname: newtitle = title.format(name=figname, number=fignum) else: From 2a3981f805c352bfc6622fbd741d34a28c31dd6b Mon Sep 17 00:00:00 2001 From: jfbu Date: Mon, 9 Jan 2017 15:49:19 +0100 Subject: [PATCH 6/6] Update CHANGES for PR#3316 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index baf8a9bbc..4df8f047c 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugs fixed * #3268: Sphinx crashes with requests package from Debian jessie * #3284: Sphinx crashes on parallel build with an extension which raises unserializable exception +* #3315: Bibliography crashes on latex build with docclass 'memoir' Release 1.5.1 (released Dec 13, 2016)