From 5b7d237db380a75c27137fb37f0396f4a3dc3ecd Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Tue, 3 Jan 2017 22:24:00 +0900 Subject: [PATCH 01/38] pytest migration --- .travis.yml | 2 +- Makefile | 7 +- test-reqs.txt | 4 +- tests/conftest.py | 231 ++++ tests/coverage.py | 1158 ----------------- tests/path.py | 2 +- tests/{ => py35}/test_autodoc_py35.py | 5 +- tests/roots/test-add_enumerable_node/conf.py | 2 +- ..._enumerable_node.py => enumerable_node.py} | 0 .../conf.py | 2 +- ...test_source_parser.py => source_parser.py} | 0 tests/roots/test-add_source_parser/conf.py | 2 +- ...test_source_parser.py => source_parser.py} | 0 tests/run.py | 16 +- tests/test_apidoc.py | 184 ++- tests/test_autodoc.py | 25 +- tests/test_build.py | 8 +- tests/test_build_gettext.py | 12 +- tests/test_build_html.py | 12 +- tests/test_build_latex.py | 15 +- tests/test_catalogs.py | 11 +- tests/test_config.py | 26 +- tests/test_environment.py | 2 - tests/test_ext_graphviz.py | 33 +- tests/test_ext_inheritance_diagram.py | 6 +- tests/test_ext_intersphinx.py | 2 +- tests/test_intl.py | 3 +- tests/test_metadata.py | 8 +- tests/test_setup_command.py | 95 +- tests/test_theming.py | 11 +- tests/test_util_fileutil.py | 38 +- tests/test_util_i18n.py | 118 +- tests/test_websupport.py | 184 ++- tests/util.py | 313 +++-- 34 files changed, 797 insertions(+), 1740 deletions(-) create mode 100644 tests/conftest.py delete mode 100755 tests/coverage.py rename tests/{ => py35}/test_autodoc_py35.py (99%) rename tests/roots/test-add_enumerable_node/{test_enumerable_node.py => enumerable_node.py} (100%) rename tests/roots/test-add_source_parser-conflicts-with-users-setting/{test_source_parser.py => source_parser.py} (100%) rename tests/roots/test-add_source_parser/{test_source_parser.py => source_parser.py} (100%) diff --git a/.travis.yml b/.travis.yml index 09b89a7ae..0217e5dad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ python: - "pypy" env: global: - - TEST='-v --with-timer --timer-top-n 25' + - TEST='-v --durations 25' - PYTHONFAULTHANDLER=x - PYTHONWARNINGS=all matrix: diff --git a/Makefile b/Makefile index 652ade4f4..887f3e2a7 100644 --- a/Makefile +++ b/Makefile @@ -72,14 +72,13 @@ reindent: @$(PYTHON) utils/reindent.py -r -n . test: - @cd tests; $(PYTHON) run.py -I py35 -d -m '^[tT]est' $(TEST) + @cd tests; $(PYTHON) run.py --ignore py35 -v $(TEST) test-async: - @cd tests; $(PYTHON) run.py -d -m '^[tT]est' $(TEST) + @cd tests; $(PYTHON) run.py -v $(TEST) covertest: - @cd tests; $(PYTHON) run.py -d -m '^[tT]est' --with-coverage \ - --cover-package=sphinx $(TEST) + @cd tests; $(PYTHON) run.py -v --cov=sphinx --junitxml=.junit.xml $(TEST) build: @$(PYTHON) setup.py build diff --git a/test-reqs.txt b/test-reqs.txt index b53adbfe5..1877886c1 100644 --- a/test-reqs.txt +++ b/test-reqs.txt @@ -1,6 +1,6 @@ flake8 -nose -nose-timer +pytest>=3.0 +pytest-cov mock six>=1.4 Jinja2>=2.3 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..9a021169e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +import sys +import subprocess + +import pytest +from six import StringIO, string_types + +from util import SphinxTestApp, path + + +@pytest.fixture +def app_params(request): + """ + parameters that is specified by 'pytest.mark.sphinx' for + sphinx.application.Sphinx initialization + """ + markers = request.node.get_marker("sphinx") + pargs = {} + kwargs = {} + + if markers is not None: + # to avoid stacking positional args + for info in reversed(list(markers)): + for i, a in enumerate(info.args): + pargs[i] = a + kwargs.update(info.kwargs) + + args = [pargs[i] for i in sorted(pargs.keys())] + return args, kwargs + + +@pytest.fixture +def test_params(request): + """ + test parameters that is specified by 'pytest.mark.testenv' + + :param bool build: + If True, 'app' fixture will be build before test function is called. + Default is False. + :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('testenv') + kwargs = env.kwargs if env else {} + result = { + 'build': False, # pre build in fixture + 'specific_srcdir': None, + 'shared_result': None, + } + result.update(kwargs) + + if (result['shared_result'] and + not isinstance(result['shared_result'], string_types)): + r = 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 + + +@pytest.fixture(scope='function') +def app(test_params, app_params, make_app, shared_result): + """ + provides sphinx.application.Sphinx object + """ + args, kwargs = app_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) + + app_ = make_app(*args, **kwargs) + + if test_params['build']: + # if listdir is not empty, we can use built cache + if not app_.outdir.listdir(): + app_.build() + yield app_ + + if test_params['shared_result']: + shared_result.store(test_params['shared_result'], app_) + + +@pytest.fixture(scope='function') +def status(app): + """ + compat for testing with previous @with_app decorator + """ + return app._status + + +@pytest.fixture(scope='function') +def warning(app): + """ + compat for testing with previous @with_app decorator + """ + return app._warning + + +@pytest.fixture() +def make_app(): + """ + provides make_app function to initialize SphinxTestApp instance. + if you want to initialize 'app' in your test function. please use this + instead of using SphinxTestApp class directory. + """ + apps = [] + syspath = sys.path[:] + + def make(*args, **kwargs): + status, warning = StringIO(), StringIO() + kwargs.setdefault('status', status) + kwargs.setdefault('warning', warning) + app_ = SphinxTestApp(*args, **kwargs) + apps.append(app_) + return app_ + yield make + + sys.path[:] = syspath + for app_ in apps: + 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): + """ + The test will be skipped when using 'if_graphviz_found' fixture and graphviz + dot command is not found. + """ + graphviz_dot = getattr(app.config, 'graphviz_dot', '') + try: + if graphviz_dot: + dot = subprocess.Popen([graphviz_dot, '-V'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) # show version + dot.communicate() + return + except OSError: # No such file or directory + pass + + pytest.skip('graphviz "dot" is not available') + + +@pytest.fixture +def tempdir(tmpdir): + """ + temporary directory that wrapped with `path` class. + this fixture is for compat with old test implementation. + """ + return path(tmpdir) + + +def pytest_addoption(parser): + """ + the test that have pytest.mark.env('foobar') will be skipped when + '-S foobar' command-line option is provided. + """ + parser.addoption("-S", action="store", metavar="NAME", + help="skip tests matching the environment NAME.") + + +def pytest_configure(config): + """ + the test that have pytest.mark.env('foobar') will be skipped when + '-S foobar' command-line option is provided. + """ + # register an additional marker + config.addinivalue_line("markers", + "env(name): mark test to run only on named environment") + + +def pytest_runtest_setup(item): + """ + the test that have pytest.mark.env('foobar') will be skipped when + '-S foobar' command-line option is provided. + """ + envmarker = item.get_marker("env") + if envmarker is not None: + envname = envmarker.args[0] + if envname == item.config.getoption("-S"): + pytest.skip("skip test %r" % envname) diff --git a/tests/coverage.py b/tests/coverage.py deleted file mode 100755 index cd36e218a..000000000 --- a/tests/coverage.py +++ /dev/null @@ -1,1158 +0,0 @@ -#!/usr/bin/python -# -# Perforce Defect Tracking Integration Project -# -# -# COVERAGE.PY -- COVERAGE TESTING -# -# Gareth Rees, Ravenbrook Limited, 2001-12-04 -# Ned Batchelder, 2004-12-12 -# http://nedbatchelder.com/code/modules/coverage.html -# -# -# 1. INTRODUCTION -# -# This module provides coverage testing for Python code. -# -# The intended readership is all Python developers. -# -# This document is not confidential. -# -# See [GDR 2001-12-04a] for the command-line interface, programmatic -# interface and limitations. See [GDR 2001-12-04b] for requirements and -# design. - -r"""Usage: - -coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...] - Execute module, passing the given command-line arguments, collecting - coverage data. With the -p option, write to a temporary file containing - the machine name and process ID. - -coverage.py -e - Erase collected coverage data. - -coverage.py -c - Collect data from multiple coverage files (as created by -p option above) - and store it into a single file representing the union of the coverage. - -coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ... - Report on the statement coverage for the given files. With the -m - option, show line numbers of the statements that weren't executed. - -coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ... - Make annotated copies of the given files, marking statements that - are executed with > and statements that are missed with !. With - the -d option, make the copies in that directory. Without the -d - option, make each copy in the same directory as the original. - --o dir,dir2,... - Omit reporting or annotating files when their filename path starts with - a directory listed in the omit list. - e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits - -Coverage data is saved in the file .coverage by default. Set the -COVERAGE_FILE environment variable to save it somewhere else. -""" -from __future__ import print_function - -__version__ = "2.85.20080914" # see detailed history at the end of this file. - -import compiler -import compiler.visitor -import glob -import os -import re -import string -import symbol -import sys -import atexit -import threading -import token -import zipimport -from socket import gethostname - -from six import string_types - - -# 2. IMPLEMENTATION -# -# This uses the "singleton" pattern. -# -# The word "morf" means a module object (from which the source file can -# be deduced by suitable manipulation of the __file__ attribute) or a -# filename. -# -# When we generate a coverage report we have to canonicalize every -# filename in the coverage dictionary just in case it refers to the -# module we are reporting on. It seems a shame to throw away this -# information so the data in the coverage dictionary is transferred to -# the 'cexecuted' dictionary under the canonical filenames. -# -# The coverage dictionary is called "c" and the trace function "t". The -# reason for these short names is that Python looks up variables by name -# at runtime and so execution time depends on the length of variables! -# In the bottleneck of this application it's appropriate to abbreviate -# names to increase speed. - -class StatementFindingAstVisitor(compiler.visitor.ASTVisitor): - """ A visitor for a parsed Abstract Syntax Tree which finds executable - statements. - """ - def __init__(self, statements, excluded, suite_spots): - compiler.visitor.ASTVisitor.__init__(self) - self.statements = statements - self.excluded = excluded - self.suite_spots = suite_spots - self.excluding_suite = 0 - - def doRecursive(self, node): - for n in node.getChildNodes(): - self.dispatch(n) - - visitStmt = visitModule = doRecursive - - def doCode(self, node): - if hasattr(node, 'decorators') and node.decorators: - self.dispatch(node.decorators) - self.recordAndDispatch(node.code) - else: - self.doSuite(node, node.code) - - visitFunction = visitClass = doCode - - def getFirstLine(self, node): - # Find the first line in the tree node. - lineno = node.lineno - for n in node.getChildNodes(): - f = self.getFirstLine(n) - if lineno and f: - lineno = min(lineno, f) - else: - lineno = lineno or f - return lineno - - def getLastLine(self, node): - # Find the first line in the tree node. - lineno = node.lineno - for n in node.getChildNodes(): - lineno = max(lineno, self.getLastLine(n)) - return lineno - - def doStatement(self, node): - self.recordLine(self.getFirstLine(node)) - - visitAssert = visitAssign = visitAssTuple = visitPrint = \ - visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ - doStatement - - def visitPass(self, node): - # Pass statements have weird interactions with docstrings. If this - # pass statement is part of one of those pairs, claim that the statement - # is on the later of the two lines. - l = node.lineno - if l: - lines = self.suite_spots.get(l, [l,l]) - self.statements[lines[1]] = 1 - - def visitDiscard(self, node): - # Discard nodes are statements that execute an expression, but then - # discard the results. This includes function calls, so we can't - # ignore them all. But if the expression is a constant, the statement - # won't be "executed", so don't count it now. - if node.expr.__class__.__name__ != 'Const': - self.doStatement(node) - - def recordNodeLine(self, node): - # Stmt nodes often have None, but shouldn't claim the first line of - # their children (because the first child might be an ignorable line - # like "global a"). - if node.__class__.__name__ != 'Stmt': - return self.recordLine(self.getFirstLine(node)) - else: - return 0 - - def recordLine(self, lineno): - # Returns a bool, whether the line is included or excluded. - if lineno: - # Multi-line tests introducing suites have to get charged to their - # keyword. - if lineno in self.suite_spots: - lineno = self.suite_spots[lineno][0] - # If we're inside an excluded suite, record that this line was - # excluded. - if self.excluding_suite: - self.excluded[lineno] = 1 - return 0 - # If this line is excluded, or suite_spots maps this line to - # another line that is exlcuded, then we're excluded. - elif lineno in self.excluded or \ - lineno in self.suite_spots and \ - self.suite_spots[lineno][1] in self.excluded: - return 0 - # Otherwise, this is an executable line. - else: - self.statements[lineno] = 1 - return 1 - return 0 - - default = recordNodeLine - - def recordAndDispatch(self, node): - self.recordNodeLine(node) - self.dispatch(node) - - def doSuite(self, intro, body, exclude=0): - exsuite = self.excluding_suite - if exclude or (intro and not self.recordNodeLine(intro)): - self.excluding_suite = 1 - self.recordAndDispatch(body) - self.excluding_suite = exsuite - - def doPlainWordSuite(self, prevsuite, suite): - # Finding the exclude lines for else's is tricky, because they aren't - # present in the compiler parse tree. Look at the previous suite, - # and find its last line. If any line between there and the else's - # first line are excluded, then we exclude the else. - lastprev = self.getLastLine(prevsuite) - firstelse = self.getFirstLine(suite) - for l in range(lastprev+1, firstelse): - if l in self.suite_spots: - self.doSuite(None, suite, exclude=l in self.excluded) - break - else: - self.doSuite(None, suite) - - def doElse(self, prevsuite, node): - if node.else_: - self.doPlainWordSuite(prevsuite, node.else_) - - def visitFor(self, node): - self.doSuite(node, node.body) - self.doElse(node.body, node) - - visitWhile = visitFor - - def visitIf(self, node): - # The first test has to be handled separately from the rest. - # The first test is credited to the line with the "if", but the others - # are credited to the line with the test for the elif. - self.doSuite(node, node.tests[0][1]) - for t, n in node.tests[1:]: - self.doSuite(t, n) - self.doElse(node.tests[-1][1], node) - - def visitTryExcept(self, node): - self.doSuite(node, node.body) - for i in range(len(node.handlers)): - a, b, h = node.handlers[i] - if not a: - # It's a plain "except:". Find the previous suite. - if i > 0: - prev = node.handlers[i-1][2] - else: - prev = node.body - self.doPlainWordSuite(prev, h) - else: - self.doSuite(a, h) - self.doElse(node.handlers[-1][2], node) - - def visitTryFinally(self, node): - self.doSuite(node, node.body) - self.doPlainWordSuite(node.body, node.final) - - def visitWith(self, node): - self.doSuite(node, node.body) - - def visitGlobal(self, node): - # "global" statements don't execute like others (they don't call the - # trace function), so don't record their line numbers. - pass - -the_coverage = None - -class CoverageException(Exception): - pass - -class coverage: - # Name of the cache file (unless environment variable is set). - cache_default = ".coverage" - - # Environment variable naming the cache file. - cache_env = "COVERAGE_FILE" - - # A dictionary with an entry for (Python source file name, line number - # in that file) if that line has been executed. - c = {} - - # A map from canonical Python source file name to a dictionary in - # which there's an entry for each line number that has been - # executed. - cexecuted = {} - - # Cache of results of calling the analysis2() method, so that you can - # specify both -r and -a without doing double work. - analysis_cache = {} - - # Cache of results of calling the canonical_filename() method, to - # avoid duplicating work. - canonical_filename_cache = {} - - def __init__(self): - global the_coverage - if the_coverage: - raise CoverageException("Only one coverage object allowed.") - self.usecache = 1 - self.cache = None - self.parallel_mode = False - self.exclude_re = '' - self.nesting = 0 - self.cstack = [] - self.xstack = [] - self.relative_dir = self.abs_file(os.curdir)+os.sep - self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]') - - # t(f, x, y). This method is passed to sys.settrace as a trace function. - # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and - # the arguments and return value of the trace function. - # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code - # objects. - - def t(self, f, w, unused): #pragma: no cover - if w == 'line': - self.c[(f.f_code.co_filename, f.f_lineno)] = 1 - #-for c in self.cstack: - #- c[(f.f_code.co_filename, f.f_lineno)] = 1 - return self.t - - def help(self, error=None): #pragma: no cover - if error: - print(error) - print() - print(__doc__) - sys.exit(1) - - def command_line(self, argv, help_fn=None): - import getopt - help_fn = help_fn or self.help - settings = {} - optmap = { - '-a': 'annotate', - '-c': 'collect', - '-d:': 'directory=', - '-e': 'erase', - '-h': 'help', - '-i': 'ignore-errors', - '-m': 'show-missing', - '-p': 'parallel-mode', - '-r': 'report', - '-x': 'execute', - '-o:': 'omit=', - } - short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') - long_opts = optmap.values() - options, args = getopt.getopt(argv, short_opts, long_opts) - for o, a in options: - if o in optmap: - settings[optmap[o]] = 1 - elif o + ':' in optmap: - settings[optmap[o + ':']] = a - elif o[2:] in long_opts: - settings[o[2:]] = 1 - elif o[2:] + '=' in long_opts: - settings[o[2:]+'='] = a - else: #pragma: no cover - # Can't get here, because getopt won't return anything unknown. - pass - - if settings.get('help'): - help_fn() - - for i in ['erase', 'execute']: - for j in ['annotate', 'report', 'collect']: - if settings.get(i) and settings.get(j): - help_fn("You can't specify the '%s' and '%s' " - "options at the same time." % (i, j)) - - args_needed = (settings.get('execute') - or settings.get('annotate') - or settings.get('report')) - action = (settings.get('erase') - or settings.get('collect') - or args_needed) - if not action: - help_fn("You must specify at least one of -e, -x, -c, -r, or -a.") - if not args_needed and args: - help_fn("Unexpected arguments: %s" % " ".join(args)) - - self.parallel_mode = settings.get('parallel-mode') - self.get_ready() - - if settings.get('erase'): - self.erase() - if settings.get('execute'): - if not args: - help_fn("Nothing to do.") - sys.argv = args - self.start() - import __main__ - sys.path[0] = os.path.dirname(sys.argv[0]) - exec(compile(open(sys.argv[0]).read(), sys.argv[0], 'exec'), __main__.__dict__) - if settings.get('collect'): - self.collect() - if not args: - args = list(self.cexecuted.keys()) - - ignore_errors = settings.get('ignore-errors') - show_missing = settings.get('show-missing') - directory = settings.get('directory=') - - omit = settings.get('omit=') - if omit is not None: - omit = [self.abs_file(p) for p in omit.split(',')] - else: - omit = [] - - if settings.get('report'): - self.report(args, show_missing, ignore_errors, omit_prefixes=omit) - if settings.get('annotate'): - self.annotate(args, directory, ignore_errors, omit_prefixes=omit) - - def use_cache(self, usecache, cache_file=None): - self.usecache = usecache - if cache_file and not self.cache: - self.cache_default = cache_file - - def get_ready(self, parallel_mode=False): - if self.usecache and not self.cache: - self.cache = os.environ.get(self.cache_env, self.cache_default) - if self.parallel_mode: - self.cache += "." + gethostname() + "." + str(os.getpid()) - self.restore() - self.analysis_cache = {} - - def start(self, parallel_mode=False): - self.get_ready() - if self.nesting == 0: #pragma: no cover - sys.settrace(self.t) - if hasattr(threading, 'settrace'): - threading.settrace(self.t) - self.nesting += 1 - - def stop(self): - self.nesting -= 1 - if self.nesting == 0: #pragma: no cover - sys.settrace(None) - if hasattr(threading, 'settrace'): - threading.settrace(None) - - def erase(self): - self.get_ready() - self.c = {} - self.analysis_cache = {} - self.cexecuted = {} - if self.cache and os.path.exists(self.cache): - os.remove(self.cache) - - def exclude(self, re): - if self.exclude_re: - self.exclude_re += "|" - self.exclude_re += "(" + re + ")" - - def begin_recursive(self): - self.cstack.append(self.c) - self.xstack.append(self.exclude_re) - - def end_recursive(self): - self.c = self.cstack.pop() - self.exclude_re = self.xstack.pop() - - # save(). Save coverage data to the coverage cache. - - def save(self): - if self.usecache and self.cache: - self.canonicalize_filenames() - import marshal - with open(self.cache, 'wb') as cache: - marshal.dump(self.cexecuted, cache) - - # restore(). Restore coverage data from the coverage cache (if it exists). - - def restore(self): - self.c = {} - self.cexecuted = {} - assert self.usecache - if os.path.exists(self.cache): - self.cexecuted = self.restore_file(self.cache) - - def restore_file(self, file_name): - try: - import marshal - with open(file_name, 'rb') as cache: - cexecuted = marshal.load(cache) - if isinstance(cexecuted, dict): - return cexecuted - else: - return {} - except: - return {} - - # collect(). Collect data in multiple files produced by parallel mode - - def collect(self): - cache_dir, local = os.path.split(self.cache) - for f in os.listdir(cache_dir or '.'): - if not f.startswith(local): - continue - - full_path = os.path.join(cache_dir, f) - cexecuted = self.restore_file(full_path) - self.merge_data(cexecuted) - - def merge_data(self, new_data): - for file_name, file_data in new_data.items(): - if file_name in self.cexecuted: - self.merge_file_data(self.cexecuted[file_name], file_data) - else: - self.cexecuted[file_name] = file_data - - def merge_file_data(self, cache_data, new_data): - for line_number in new_data.keys(): - if line_number not in cache_data: - cache_data[line_number] = new_data[line_number] - - def abs_file(self, filename): - """ Helper function to turn a filename into an absolute normalized - filename. - """ - return os.path.normcase(os.path.abspath(os.path.realpath(filename))) - - def get_zip_data(self, filename): - """ Get data from `filename` if it is a zip file path, or return None - if it is not. - """ - markers = ['.zip'+os.sep, '.egg'+os.sep] - for marker in markers: - if marker in filename: - parts = filename.split(marker) - try: - zi = zipimport.zipimporter(parts[0]+marker[:-1]) - except zipimport.ZipImportError: - continue - try: - data = zi.get_data(parts[1]) - except IOError: - continue - return data - return None - - # canonical_filename(filename). Return a canonical filename for the - # file (that is, an absolute path with no redundant components and - # normalized case). See [GDR 2001-12-04b, 3.3]. - - def canonical_filename(self, filename): - if filename not in self.canonical_filename_cache: - f = filename - if os.path.isabs(f) and not os.path.exists(f): - if not self.get_zip_data(f): - f = os.path.basename(f) - if not os.path.isabs(f): - for path in [os.curdir] + sys.path: - g = os.path.join(path, f) - if os.path.exists(g): - f = g - break - cf = self.abs_file(f) - self.canonical_filename_cache[filename] = cf - return self.canonical_filename_cache[filename] - - # canonicalize_filenames(). Copy results from "c" to "cexecuted", - # canonicalizing filenames on the way. Clear the "c" map. - - def canonicalize_filenames(self): - for filename, lineno in self.c.keys(): - if filename == '': - # Can't do anything useful with exec'd strings, so skip them. - continue - f = self.canonical_filename(filename) - if f not in self.cexecuted: - self.cexecuted[f] = {} - self.cexecuted[f][lineno] = 1 - self.c = {} - - # morf_filename(morf). Return the filename for a module or file. - - def morf_filename(self, morf): - if hasattr(morf, '__file__'): - f = morf.__file__ - else: - f = morf - return self.canonical_filename(f) - - # analyze_morf(morf). Analyze the module or filename passed as - # the argument. If the source code can't be found, raise an error. - # Otherwise, return a tuple of (1) the canonical filename of the - # source code for the module, (2) a list of lines of statements - # in the source code, (3) a list of lines of excluded statements, - # and (4), a map of line numbers to multi-line line number ranges, for - # statements that cross lines. - - def analyze_morf(self, morf): - if morf in self.analysis_cache: - return self.analysis_cache[morf] - filename = self.morf_filename(morf) - ext = os.path.splitext(filename)[1] - source, sourcef = None, None - if ext == '.pyc': - if not os.path.exists(filename[:-1]): - source = self.get_zip_data(filename[:-1]) - if not source: - raise CoverageException( - "No source for compiled code '%s'." % filename - ) - filename = filename[:-1] - if not source: - with open(filename, 'rU') as sourcef: - source = sourcef.read() - try: - lines, excluded_lines, line_map = self.find_executable_statements( - source, exclude=self.exclude_re - ) - except SyntaxError as synerr: - raise CoverageException( - "Couldn't parse '%s' as Python source: '%s' at line %d" % - (filename, synerr.msg, synerr.lineno) - ) - result = filename, lines, excluded_lines, line_map - self.analysis_cache[morf] = result - return result - - def first_line_of_tree(self, tree): - while True: - if len(tree) == 3 and type(tree[2]) == type(1): - return tree[2] - tree = tree[1] - - def last_line_of_tree(self, tree): - while True: - if len(tree) == 3 and type(tree[2]) == type(1): - return tree[2] - tree = tree[-1] - - def find_docstring_pass_pair(self, tree, spots): - for i in range(1, len(tree)): - if (self.is_string_constant(tree[i]) - and self.is_pass_stmt(tree[i+1])): - first_line = self.first_line_of_tree(tree[i]) - last_line = self.last_line_of_tree(tree[i+1]) - self.record_multiline(spots, first_line, last_line) - - def is_string_constant(self, tree): - try: - return (tree[0] == symbol.stmt - and tree[1][1][1][0] == symbol.expr_stmt) - except: - return False - - def is_pass_stmt(self, tree): - try: - return (tree[0] == symbol.stmt - and tree[1][1][1][0] == symbol.pass_stmt) - except: - return False - - def record_multiline(self, spots, i, j): - for l in range(i, j+1): - spots[l] = (i, j) - - def get_suite_spots(self, tree, spots): - """ Analyze a parse tree to find suite introducers which span a number - of lines. - """ - for i in range(1, len(tree)): - if type(tree[i]) == type(()): - if tree[i][0] == symbol.suite: - # Found a suite, look back for the colon and keyword. - lineno_colon = lineno_word = None - for j in range(i-1, 0, -1): - if tree[j][0] == token.COLON: - # Colons are never executed themselves: we want the - # line number of the last token before the colon. - lineno_colon = self.last_line_of_tree(tree[j-1]) - elif tree[j][0] == token.NAME: - if tree[j][1] == 'elif': - # Find the line number of the first non-terminal - # after the keyword. - t = tree[j+1] - while t and token.ISNONTERMINAL(t[0]): - t = t[1] - if t: - lineno_word = t[2] - else: - lineno_word = tree[j][2] - break - elif tree[j][0] == symbol.except_clause: - # "except" clauses look like: - # ('except_clause', ('NAME', 'except', lineno), ...) - if tree[j][1][0] == token.NAME: - lineno_word = tree[j][1][2] - break - if lineno_colon and lineno_word: - # Found colon and keyword, mark all the lines - # between the two with the two line numbers. - self.record_multiline(spots, lineno_word, lineno_colon) - - # "pass" statements are tricky: different versions of Python - # treat them differently, especially in the common case of a - # function with a doc string and a single pass statement. - self.find_docstring_pass_pair(tree[i], spots) - - elif tree[i][0] == symbol.simple_stmt: - first_line = self.first_line_of_tree(tree[i]) - last_line = self.last_line_of_tree(tree[i]) - if first_line != last_line: - self.record_multiline(spots, first_line, last_line) - self.get_suite_spots(tree[i], spots) - - def find_executable_statements(self, text, exclude=None): - # Find lines which match an exclusion pattern. - excluded = {} - suite_spots = {} - if exclude: - reExclude = re.compile(exclude) - lines = text.split('\n') - for i in range(len(lines)): - if reExclude.search(lines[i]): - excluded[i+1] = 1 - - # Parse the code and analyze the parse tree to find out which statements - # are multiline, and where suites begin and end. - import parser - tree = parser.suite(text+'\n\n').totuple(1) - self.get_suite_spots(tree, suite_spots) - #print "Suite spots:", suite_spots - - # Use the compiler module to parse the text and find the executable - # statements. We add newlines to be impervious to final partial lines. - statements = {} - ast = compiler.parse(text+'\n\n') - visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) - compiler.walk(ast, visitor, walker=visitor) - - lines = sorted(statements.keys()) - excluded_lines = sorted(excluded.keys()) - return lines, excluded_lines, suite_spots - - # format_lines(statements, lines). Format a list of line numbers - # for printing by coalescing groups of lines as long as the lines - # represent consecutive statements. This will coalesce even if - # there are gaps between statements, so if statements = - # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then - # format_lines will return "1-2, 5-11, 13-14". - - def format_lines(self, statements, lines): - pairs = [] - i = 0 - j = 0 - start = None - pairs = [] - while i < len(statements) and j < len(lines): - if statements[i] == lines[j]: - if start is None: - start = lines[j] - end = lines[j] - j = j + 1 - elif start: - pairs.append((start, end)) - start = None - i = i + 1 - if start: - pairs.append((start, end)) - def stringify(pair): - start, end = pair - if start == end: - return "%d" % start - else: - return "%d-%d" % (start, end) - ret = string.join(map(stringify, pairs), ", ") - return ret - - # Backward compatibility with version 1. - def analysis(self, morf): - f, s, _, m, mf = self.analysis2(morf) - return f, s, m, mf - - def analysis2(self, morf): - filename, statements, excluded, line_map = self.analyze_morf(morf) - self.canonicalize_filenames() - if filename not in self.cexecuted: - self.cexecuted[filename] = {} - missing = [] - for line in statements: - lines = line_map.get(line, [line, line]) - for l in range(lines[0], lines[1]+1): - if l in self.cexecuted[filename]: - break - else: - missing.append(line) - return (filename, statements, excluded, missing, - self.format_lines(statements, missing)) - - def relative_filename(self, filename): - """ Convert filename to relative filename from self.relative_dir. - """ - return filename.replace(self.relative_dir, "") - - def morf_name(self, morf): - """ Return the name of morf as used in report. - """ - if hasattr(morf, '__name__'): - return morf.__name__ - else: - return self.relative_filename(os.path.splitext(morf)[0]) - - def filter_by_prefix(self, morfs, omit_prefixes): - """ Return list of morfs where the morf name does not begin - with any one of the omit_prefixes. - """ - filtered_morfs = [] - for morf in morfs: - for prefix in omit_prefixes: - if self.morf_name(morf).startswith(prefix): - break - else: - filtered_morfs.append(morf) - - return filtered_morfs - - def morf_name_compare(self, x, y): - return cmp(self.morf_name(x), self.morf_name(y)) - - def report(self, morfs, show_missing=1, ignore_errors=0, file=None, - omit_prefixes=[]): - if not isinstance(morfs, list): - morfs = [morfs] - # On windows, the shell doesn't expand wildcards. Do it here. - globbed = [] - for morf in morfs: - if isinstance(morf, string_types): - globbed.extend(glob.glob(morf)) - else: - globbed.append(morf) - morfs = globbed - - morfs = self.filter_by_prefix(morfs, omit_prefixes) - morfs.sort(self.morf_name_compare) - - max_name = max(5, *map(len, map(self.morf_name, morfs))) - fmt_name = "%%- %ds " % max_name - fmt_err = fmt_name + "%s: %s" - header = fmt_name % "Name" + " Stmts Exec Cover" - fmt_coverage = fmt_name + "% 6d % 6d % 5d%%" - if show_missing: - header = header + " Missing" - fmt_coverage = fmt_coverage + " %s" - if not file: - file = sys.stdout - print(header, file=file) - print("-" * len(header), file=file) - total_statements = 0 - total_executed = 0 - for morf in morfs: - name = self.morf_name(morf) - try: - _, statements, _, missing, readable = self.analysis2(morf) - n = len(statements) - m = n - len(missing) - if n > 0: - pc = 100.0 * m / n - else: - pc = 100.0 - args = (name, n, m, pc) - if show_missing: - args = args + (readable,) - print(fmt_coverage % args, file=file) - total_statements = total_statements + n - total_executed = total_executed + m - except KeyboardInterrupt: #pragma: no cover - raise - except: - if not ignore_errors: - typ, msg = sys.exc_info()[:2] - print(fmt_err % (name, typ, msg), file=file) - if len(morfs) > 1: - print("-" * len(header), file=file) - if total_statements > 0: - pc = 100.0 * total_executed / total_statements - else: - pc = 100.0 - args = ("TOTAL", total_statements, total_executed, pc) - if show_missing: - args = args + ("",) - print(fmt_coverage % args, file=file) - - # annotate(morfs, ignore_errors). - - blank_re = re.compile(r"\s*(#|$)") - else_re = re.compile(r"\s*else\s*:\s*(#|$)") - - def annotate(self, morfs, directory=None, ignore_errors=0, - omit_prefixes=[]): - morfs = self.filter_by_prefix(morfs, omit_prefixes) - for morf in morfs: - try: - (filename, statements, excluded, - missing, _) = self.analysis2(morf) - self.annotate_file(filename, statements, excluded, missing, - directory) - except KeyboardInterrupt: - raise - except: - if not ignore_errors: - raise - - def annotate_file(self, filename, statements, excluded, missing, - directory=None): - source = open(filename, 'r') - if directory: - dest_file = os.path.join(directory, - os.path.basename(filename) - + ',cover') - else: - dest_file = filename + ',cover' - dest = open(dest_file, 'w') - lineno = 0 - i = 0 - j = 0 - covered = 1 - while 1: - line = source.readline() - if line == '': - break - lineno = lineno + 1 - while i < len(statements) and statements[i] < lineno: - i = i + 1 - while j < len(missing) and missing[j] < lineno: - j = j + 1 - if i < len(statements) and statements[i] == lineno: - covered = j >= len(missing) or missing[j] > lineno - if self.blank_re.match(line): - dest.write(' ') - elif self.else_re.match(line): - # Special logic for lines containing only 'else:'. - # See [GDR 2001-12-04b, 3.2]. - if i >= len(statements) and j >= len(missing): - dest.write('! ') - elif i >= len(statements) or j >= len(missing): - dest.write('> ') - elif statements[i] == missing[j]: - dest.write('! ') - else: - dest.write('> ') - elif lineno in excluded: - dest.write('- ') - elif covered: - dest.write('> ') - else: - dest.write('! ') - dest.write(line) - source.close() - dest.close() - -# Singleton object. -the_coverage = coverage() - -# Module functions call methods in the singleton object. -def use_cache(*args, **kw): - return the_coverage.use_cache(*args, **kw) - -def start(*args, **kw): - return the_coverage.start(*args, **kw) - -def stop(*args, **kw): - return the_coverage.stop(*args, **kw) - -def erase(*args, **kw): - return the_coverage.erase(*args, **kw) - -def begin_recursive(*args, **kw): - return the_coverage.begin_recursive(*args, **kw) - -def end_recursive(*args, **kw): - return the_coverage.end_recursive(*args, **kw) - -def exclude(*args, **kw): - return the_coverage.exclude(*args, **kw) - -def analysis(*args, **kw): - return the_coverage.analysis(*args, **kw) - -def analysis2(*args, **kw): - return the_coverage.analysis2(*args, **kw) - -def report(*args, **kw): - return the_coverage.report(*args, **kw) - -def annotate(*args, **kw): - return the_coverage.annotate(*args, **kw) - -def annotate_file(*args, **kw): - return the_coverage.annotate_file(*args, **kw) - -atexit.register(the_coverage.save) - -def main(): - the_coverage.command_line(sys.argv[1:]) - -# Command-line interface. -if __name__ == '__main__': - main() - - -# A. REFERENCES -# -# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; -# Ravenbrook Limited; 2001-12-04; -# . -# -# [GDR 2001-12-04b] "Statement coverage for Python: design and -# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04; -# . -# -# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)"; -# Guide van Rossum; 2001-07-20; -# . -# -# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum; -# 2001-07-20; . -# -# -# B. DOCUMENT HISTORY -# -# 2001-12-04 GDR Created. -# -# 2001-12-06 GDR Added command-line interface and source code -# annotation. -# -# 2001-12-09 GDR Moved design and interface to separate documents. -# -# 2001-12-10 GDR Open cache file as binary on Windows. Allow -# simultaneous -e and -x, or -a and -r. -# -# 2001-12-12 GDR Added command-line help. Cache analysis so that it -# only needs to be done once when you specify -a and -r. -# -# 2001-12-13 GDR Improved speed while recording. Portable between -# Python 1.5.2 and 2.1.1. -# -# 2002-01-03 GDR Module-level functions work correctly. -# -# 2002-01-07 GDR Update sys.path when running a file with the -x option, -# so that it matches the value the program would get if it were run on -# its own. -# -# 2004-12-12 NMB Significant code changes. -# - Finding executable statements has been rewritten so that docstrings and -# other quirks of Python execution aren't mistakenly identified as missing -# lines. -# - Lines can be excluded from consideration, even entire suites of lines. -# - The filesystem cache of covered lines can be disabled programmatically. -# - Modernized the code. -# -# 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior -# and add 'analysis2'. Add a global for 'annotate', and factor it, adding -# 'annotate_file'. -# -# 2004-12-31 NMB Allow for keyword arguments in the module global functions. -# Thanks, Allen. -# -# 2005-12-02 NMB Call threading.settrace so that all threads are measured. -# Thanks Martin Fuzzey. Add a file argument to report so that reports can be -# captured to a different destination. -# -# 2005-12-03 NMB coverage.py can now measure itself. -# -# 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames, -# and sorting and omitting files to report on. -# -# 2006-07-23 NMB Applied Joseph Tate's patch for function decorators. -# -# 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument -# handling. -# -# 2006-08-22 NMB Applied Geoff Bache's parallel mode patch. -# -# 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line -# logic for parallel mode and collect. -# -# 2006-08-25 NMB "#pragma: nocover" is excluded by default. -# -# 2006-09-10 NMB Properly ignore docstrings and other constant expressions that -# appear in the middle of a function, a problem reported by Tim Leslie. -# Minor changes to avoid lint warnings. -# -# 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex. -# Change how parallel mode is invoked, and fix erase() so that it erases the -# cache when called programmatically. -# -# 2007-07-21 NMB In reports, ignore code executed from strings, since we can't -# do anything useful with it anyway. -# Better file handling on Linux, thanks Guillaume Chazarain. -# Better shell support on Windows, thanks Noel O'Boyle. -# Python 2.2 support maintained, thanks Catherine Proulx. -# -# 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with -# multi-line statements is now less sensitive to the exact line that Python -# reports during execution. Pass statements are handled specially so that their -# disappearance during execution won't throw off the measurement. -# -# 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the -# new with statement is counted as executable. -# -# 2007-07-29 NMB Better packaging. -# -# 2007-09-30 NMB Don't try to predict whether a file is Python source based on -# the extension. Extensionless files are often Pythons scripts. Instead, simply -# parse the file and catch the syntax errors. Hat tip to Ben Finney. -# -# 2008-05-25 NMB Open files in rU mode to avoid line ending craziness. -# Thanks, Edward Loper. -# -# 2008-09-14 NMB Add support for finding source files in eggs. -# Don't check for morf's being instances of ModuleType, instead use duck typing -# so that pseudo-modules can participate. Thanks, Imri Goldberg. -# Use os.realpath as part of the fixing of filenames so that symlinks won't -# confuse things. Thanks, Patrick Mezard. -# -# -# C. COPYRIGHT AND LICENCE -# -# Copyright 2001 Gareth Rees. All rights reserved. -# Copyright 2004-2008 Ned Batchelder. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the -# distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. -# -# $Id: coverage.py 100 2008-10-12 12:08:22Z nedbat $ diff --git a/tests/path.py b/tests/path.py index ef1f35004..0d6892776 100755 --- a/tests/path.py +++ b/tests/path.py @@ -145,7 +145,7 @@ class path(text_type): mode = 'rU' if PY2 else 'r' with open(self, mode=mode, encoding=encoding, **kwargs) as f: text = f.read() - contents = repr_as(text, '<%s contents>' % self.basename()) + contents = repr_as(text, '<%s contents: %r>' % (self.basename(), text)) return contents def bytes(self): diff --git a/tests/test_autodoc_py35.py b/tests/py35/test_autodoc_py35.py similarity index 99% rename from tests/test_autodoc_py35.py rename to tests/py35/test_autodoc_py35.py index 43cda2260..6049f7521 100644 --- a/tests/test_autodoc_py35.py +++ b/tests/py35/test_autodoc_py35.py @@ -14,7 +14,7 @@ import six import sys from util import TestApp, Struct, raises, SkipTest -from nose.tools import with_setup, eq_ +import pytest from six import StringIO from docutils.statemachine import ViewList @@ -42,6 +42,7 @@ def teardown_module(): directive = options = None +@pytest.fixture def setup_test(): global options, directive global processed_docstrings, processed_signatures, _warnings @@ -106,7 +107,7 @@ def skip_member(app, what, name, obj, skip, options): return True -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_generate(): def assert_warns(warn_str, objtype, name, **kw): inst = AutoDirective._registry[objtype](directive, name) diff --git a/tests/roots/test-add_enumerable_node/conf.py b/tests/roots/test-add_enumerable_node/conf.py index d433def93..a4801f6bb 100644 --- a/tests/roots/test-add_enumerable_node/conf.py +++ b/tests/roots/test-add_enumerable_node/conf.py @@ -4,7 +4,7 @@ import os import sys sys.path.insert(0, os.path.abspath('.')) -extensions = ['test_enumerable_node'] +extensions = ['enumerable_node'] master_doc = 'index' numfig = True diff --git a/tests/roots/test-add_enumerable_node/test_enumerable_node.py b/tests/roots/test-add_enumerable_node/enumerable_node.py similarity index 100% rename from tests/roots/test-add_enumerable_node/test_enumerable_node.py rename to tests/roots/test-add_enumerable_node/enumerable_node.py diff --git a/tests/roots/test-add_source_parser-conflicts-with-users-setting/conf.py b/tests/roots/test-add_source_parser-conflicts-with-users-setting/conf.py index db9fe54a9..c50c0d2df 100644 --- a/tests/roots/test-add_source_parser-conflicts-with-users-setting/conf.py +++ b/tests/roots/test-add_source_parser-conflicts-with-users-setting/conf.py @@ -11,7 +11,7 @@ class DummyTestParser(Parser): pass -extensions = ['test_source_parser'] +extensions = ['source_parser'] source_suffix = ['.rst', '.test'] source_parsers = { '.test': DummyTestParser diff --git a/tests/roots/test-add_source_parser-conflicts-with-users-setting/test_source_parser.py b/tests/roots/test-add_source_parser-conflicts-with-users-setting/source_parser.py similarity index 100% rename from tests/roots/test-add_source_parser-conflicts-with-users-setting/test_source_parser.py rename to tests/roots/test-add_source_parser-conflicts-with-users-setting/source_parser.py diff --git a/tests/roots/test-add_source_parser/conf.py b/tests/roots/test-add_source_parser/conf.py index f9969341a..5fc0c63d2 100644 --- a/tests/roots/test-add_source_parser/conf.py +++ b/tests/roots/test-add_source_parser/conf.py @@ -11,7 +11,7 @@ class DummyMarkdownParser(Parser): pass -extensions = ['test_source_parser'] +extensions = ['source_parser'] source_suffix = ['.rst', '.md'] source_parsers = { '.md': DummyMarkdownParser diff --git a/tests/roots/test-add_source_parser/test_source_parser.py b/tests/roots/test-add_source_parser/source_parser.py similarity index 100% rename from tests/roots/test-add_source_parser/test_source_parser.py rename to tests/roots/test-add_source_parser/source_parser.py diff --git a/tests/run.py b/tests/run.py index 273b2ee6b..72a668541 100755 --- a/tests/run.py +++ b/tests/run.py @@ -17,14 +17,14 @@ import warnings import traceback from path import path -import nose +import pytest testroot = os.path.dirname(__file__) or '.' sys.path.insert(0, os.path.abspath(os.path.join(testroot, os.path.pardir))) # check dependencies before testing print('Checking dependencies...') -for modname in ('nose', 'mock', 'six', 'docutils', 'jinja2', 'pygments', +for modname in ('pytest', 'mock', 'six', 'docutils', 'jinja2', 'pygments', 'snowballstemmer', 'babel', 'html5lib'): try: __import__(modname) @@ -50,7 +50,15 @@ 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='nose.util') warnings.filterwarnings('ignore', category=DeprecationWarning, module='site') # virtualenv -nose.main(argv=sys.argv) +# 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)) + for sub in ('root', 'roots') +] +args = sys.argv[1:] +for path in ignore_paths: + args.extend(['--ignore', path]) + +sys.exit(pytest.main(args)) diff --git a/tests/test_apidoc.py b/tests/test_apidoc.py index ff6a147ca..d44868aeb 100644 --- a/tests/test_apidoc.py +++ b/tests/test_apidoc.py @@ -11,45 +11,60 @@ from __future__ import print_function -import sys -from six import PY2 +from collections import namedtuple -from sphinx import apidoc +import pytest -from util import with_tempdir, with_app, rootdir +from sphinx.apidoc import main as apidoc_main + +from util import rootdir, remove_unicode_literals -@with_tempdir -def test_simple(tempdir): - codedir = rootdir / 'root' +@pytest.fixture() +def apidoc(tempdir, apidoc_params): + _, kwargs = apidoc_params + coderoot = kwargs.get('coderoot', (rootdir / 'root')) outdir = tempdir / 'out' - args = ['sphinx-apidoc', '-o', outdir, '-F', codedir] - apidoc.main(args) + args = ['sphinx-apidoc', '-o', outdir, '-F', coderoot] + kwargs.get('options', []) + apidoc_main(args) + return namedtuple('apidoc', 'coderoot,outdir')(coderoot, outdir) + +@pytest.fixture +def apidoc_params(request): + markers = request.node.get_marker("apidoc") + pargs = {} + kwargs = {} + + if markers is not None: + for info in reversed(list(markers)): + for i, a in enumerate(info.args): + pargs[i] = a + kwargs.update(info.kwargs) + + args = [pargs[i] for i in sorted(pargs.keys())] + return args, kwargs + + +@pytest.mark.apidoc(coderoot=(rootdir / 'root')) +def test_simple(make_app, apidoc): + outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() assert (outdir / 'autodoc_fodder.rst').isfile() assert (outdir / 'index.rst').isfile() - @with_app('text', srcdir=outdir) - def assert_build(app, status, warning): - app.build() - print(status.getvalue()) - print(warning.getvalue()) - - sys.path.append(codedir) - try: - assert_build() - finally: - sys.path.remove(codedir) + app = make_app('text', srcdir=outdir) + app.build() + print(app._status.getvalue()) + print(app._warning.getvalue()) -@with_tempdir -def test_pep_0420_enabled(tempdir): - codedir = rootdir / 'root' / 'pep_0420' - outdir = tempdir / 'out' - args = ['sphinx-apidoc', '-o', outdir, '-F', codedir, "--implicit-namespaces"] - apidoc.main(args) - +@pytest.mark.apidoc( + coderoot=(rootdir / 'root' / 'pep_0420'), + options=["--implicit-namespaces"], +) +def test_pep_0420_enabled(make_app, apidoc): + outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() assert (outdir / 'a.b.c.rst').isfile() assert (outdir / 'a.b.x.rst').isfile() @@ -66,49 +81,28 @@ def test_pep_0420_enabled(tempdir): assert "automodule:: a.b.x.y\n" in rst assert "automodule:: a.b.x\n" not in rst - @with_app('text', srcdir=outdir) - def assert_build(app, status, warning): - app.build() - print(status.getvalue()) - print(warning.getvalue()) - - sys.path.append(codedir) - try: - assert_build() - finally: - sys.path.remove(codedir) + app = make_app('text', srcdir=outdir) + app.build() + print(app._status.getvalue()) + print(app._warning.getvalue()) -@with_tempdir -def test_pep_0420_disabled(tempdir): - codedir = rootdir / 'root' / 'pep_0420' - outdir = tempdir / 'out' - args = ['sphinx-apidoc', '-o', outdir, '-F', codedir] - apidoc.main(args) - +@pytest.mark.apidoc(coderoot=(rootdir / 'root' / 'pep_0420')) +def test_pep_0420_disabled(make_app, apidoc): + outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() assert not (outdir / 'a.b.c.rst').exists() assert not (outdir / 'a.b.x.rst').exists() - @with_app('text', srcdir=outdir) - def assert_build(app, status, warning): - app.build() - print(status.getvalue()) - print(warning.getvalue()) + app = make_app('text', srcdir=outdir) + app.build() + print(app._status.getvalue()) + print(app._warning.getvalue()) - sys.path.append(codedir) - try: - assert_build() - finally: - sys.path.remove(codedir) - -@with_tempdir -def test_pep_0420_disabled_top_level_verify(tempdir): - codedir = rootdir / 'root' / 'pep_0420' / 'a' / 'b' - outdir = tempdir / 'out' - args = ['sphinx-apidoc', '-o', outdir, '-F', codedir] - apidoc.main(args) +@pytest.mark.apidoc(coderoot=(rootdir / 'root' / 'pep_0420' / 'a' / 'b')) +def test_pep_0420_disabled_top_level_verify(make_app, apidoc): + outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() assert (outdir / 'c.rst').isfile() assert not (outdir / 'x.rst').exists() @@ -119,53 +113,35 @@ def test_pep_0420_disabled_top_level_verify(tempdir): assert "automodule:: c.d\n" in rst assert "automodule:: c\n" in rst - @with_app('text', srcdir=outdir) - def assert_build(app, status, warning): - app.build() - print(status.getvalue()) - print(warning.getvalue()) + app = make_app('text', srcdir=outdir) + app.build() + print(app._status.getvalue()) + print(app._warning.getvalue()) - sys.path.append(codedir) - try: - assert_build() - finally: - sys.path.remove(codedir) - -@with_tempdir -def test_multibyte_parameters(tempdir): - codedir = rootdir / 'root' - outdir = tempdir / 'out' - args = ['sphinx-apidoc', '-o', outdir, '-F', codedir, - '--doc-project', u'プロジェクト名'.encode('utf-8'), - '--doc-author', u'著者名'.encode('utf-8'), - '--doc-version', u'バージョン'.encode('utf-8'), - '--doc-release', u'リリース'.encode('utf-8')] - apidoc.main(args) +@pytest.mark.apidoc( + coderoot=(rootdir / 'root'), + options=[ + '--doc-project', u'プロジェクト名'.encode('utf-8'), + '--doc-author', u'著者名'.encode('utf-8'), + '--doc-version', u'バージョン'.encode('utf-8'), + '--doc-release', u'リリース'.encode('utf-8'), + ], +) +def test_multibyte_parameters(make_app, apidoc): + outdir = apidoc.outdir assert (outdir / 'conf.py').isfile() assert (outdir / 'autodoc_fodder.rst').isfile() assert (outdir / 'index.rst').isfile() conf_py = (outdir / 'conf.py').text() - if PY2: - assert u"project = u'プロジェクト名'" in conf_py - assert u"author = u'著者名'" in conf_py - assert u"version = u'バージョン'" in conf_py - assert u"release = u'リリース'" in conf_py - else: - assert u"project = 'プロジェクト名'" in conf_py - assert u"author = '著者名'" in conf_py - assert u"version = 'バージョン'" in conf_py - assert u"release = 'リリース'" in conf_py + conf_py_ = remove_unicode_literals(conf_py) + assert u"project = 'プロジェクト名'" in conf_py_ + assert u"author = '著者名'" in conf_py_ + assert u"version = 'バージョン'" in conf_py_ + assert u"release = 'リリース'" in conf_py_ - @with_app('text', srcdir=outdir) - def assert_build(app, status, warning): - app.build() - print(status.getvalue()) - print(warning.getvalue()) - - sys.path.append(codedir) - try: - assert_build() - finally: - sys.path.remove(codedir) + app = make_app('text', srcdir=outdir) + app.build() + print(app._status.getvalue()) + print(app._warning.getvalue()) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 8df943ec7..22eb02b34 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -12,7 +12,7 @@ # "raises" imported for usage by autodoc from util import TestApp, Struct, raises, SkipTest # NOQA -from nose.tools import with_setup, eq_ +import pytest import enum from six import StringIO, add_metaclass @@ -41,6 +41,7 @@ def teardown_module(): directive = options = None +@pytest.fixture def setup_test(): global options, directive global processed_docstrings, processed_signatures, _warnings @@ -74,6 +75,10 @@ def setup_test(): processed_signatures = [] _warnings = [] + yield + + AutoDirective._special_attrgetters.clear() + _warnings = [] processed_docstrings = [] @@ -105,7 +110,7 @@ def skip_member(app, what, name, obj, skip, options): return True -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_parse_name(): def verify(objtype, name, result): inst = AutoDirective._registry[objtype](directive, name) @@ -147,7 +152,7 @@ def test_parse_name(): del directive.env.temp_data['autodoc:class'] -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_format_signature(): def formatsig(objtype, name, obj, args, retann): inst = AutoDirective._registry[objtype](directive, name) @@ -253,7 +258,7 @@ def test_format_signature(): '(b, c=42, *d, **e)' -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_get_doc(): def getdocl(objtype, obj, encoding=None): inst = AutoDirective._registry[objtype](directive, 'tmp') @@ -423,7 +428,7 @@ def test_get_doc(): assert getdocl('class', I) == ['Class docstring', '', 'New docstring'] -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_docstring_processing(): def process(objtype, name, obj): inst = AutoDirective._registry[objtype](directive, name) @@ -478,7 +483,7 @@ def test_docstring_processing(): app.disconnect(lid) -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_docstring_property_processing(): def genarate_docstring(objtype, name, **kw): del processed_docstrings[:] @@ -515,7 +520,7 @@ def test_docstring_property_processing(): assert 'Second line of docstring' in docstrings -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_new_documenter(): class MyDocumenter(ModuleLevelDocumenter): objtype = 'integer' @@ -543,7 +548,7 @@ def test_new_documenter(): assert_result_contains('.. py:data:: integer', 'module', 'test_autodoc') -@with_setup(setup_test, AutoDirective._special_attrgetters.clear) +@pytest.mark.usefixtures('setup_test') def test_attrgetter_using(): def assert_getter_works(objtype, name, obj, attrs=[], **kw): getattr_spy = [] @@ -575,7 +580,7 @@ def test_attrgetter_using(): assert_getter_works('class', 'test_autodoc.Class', Class, ['meth', 'inheritedmeth']) -@with_setup(setup_test) +@pytest.mark.usefixtures('setup_test') def test_generate(): def assert_warns(warn_str, objtype, name, **kw): inst = AutoDirective._registry[objtype](directive, name) @@ -1084,7 +1089,7 @@ def test_type_hints(): raise SkipTest('Cannot import Python code with function annotations') def verify_arg_spec(f, expected): - eq_(formatargspec(f, *getargspec(f)), expected) + assert formatargspec(f, *getargspec(f)) == expected # Class annotations verify_arg_spec(f0, '(x: int, y: numbers.Integral) -> None') diff --git a/tests/test_build.py b/tests/test_build.py index d61291a2f..9f8db6b86 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -78,12 +78,12 @@ def test_build_all(): @with_tempdir -def test_master_doc_not_found(tmpdir): - (tmpdir / 'conf.py').write_text('master_doc = "index"') - assert tmpdir.listdir() == ['conf.py'] +def test_master_doc_not_found(tempdir): + (tempdir / 'conf.py').write_text('master_doc = "index"') + assert tempdir.listdir() == ['conf.py'] try: - app = TestApp(buildername='dummy', srcdir=tmpdir) + app = TestApp(buildername='dummy', srcdir=tempdir) app.builder.build_all() assert False # SphinxError not raised except Exception as exc: diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py index 2dc62b838..0ba6d5534 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -15,9 +15,9 @@ import re import gettext from subprocess import Popen, PIPE -from nose.tools import assert_true, assert_equal - -from util import with_app, gen_with_app, SkipTest, assert_in +from util import ( + with_app, gen_with_app, SkipTest, assert_in, assert_true, assert_equal +) @gen_with_app('gettext', srcdir='root-gettext') @@ -76,7 +76,7 @@ def test_all(app, status, warning): yield assert_equal, _("Testing various markup"), u"Testing various markup" -@with_app('gettext', testroot='intl', +@with_app('gettext', testroot='intl', srcdir='gettext', confoverrides={'gettext_compact': False}) def test_gettext_index_entries(app, status, warning): # regression test for #976 @@ -123,7 +123,7 @@ def test_gettext_index_entries(app, status, warning): assert msgids == [] -@with_app('gettext', testroot='intl', +@with_app('gettext', testroot='intl', srcdir='gettext', confoverrides={'gettext_compact': False, 'gettext_additional_targets': []}) def test_gettext_disable_index_entries(app, status, warning): # regression test for #976 @@ -155,7 +155,7 @@ def test_gettext_disable_index_entries(app, status, warning): assert msgids == [] -@with_app(buildername='gettext', testroot='intl') +@with_app(buildername='gettext', testroot='intl', srcdir='gettext') def test_gettext_template(app, status, warning): 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 d8aff88ab..e45c70052 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -24,10 +24,7 @@ TREE_BUILDER = getTreeBuilder('etree', implementation=ElementTree) HTML_PARSER = HTMLParser(TREE_BUILDER, namespaceHTMLElements=False) ENV_WARNINGS = """\ -(%(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ -WARNING: duplicate object description of autodoc_fodder.MarkupError, other \ -instance in %(root)s/autodoc.rst, use :noindex: for one of them -)?%(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ +%(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ WARNING: Explicit markup ends without a blank line; unexpected unindent. %(root)s/index.rst:\\d+: WARNING: Encoding 'utf-8-sig' used for reading included \ file u'%(root)s/wrongenc.inc' seems to be wrong, try giving an :encoding: option @@ -36,7 +33,7 @@ file u'%(root)s/wrongenc.inc' seems to be wrong, try giving an :encoding: option %(root)s/index.rst:\\d+: WARNING: download file not readable: %(root)s/nonexisting.png %(root)s/index.rst:\\d+: WARNING: invalid single index entry u'' %(root)s/undecodable.rst:\\d+: WARNING: undecodable source characters, replacing \ -with "\\?": b?'here: >>>(\\\\|/)xbb<<<' +with "\\?": b?'here: >>>(\\\\|/)xbb<<<((\\\\|/)r)?' """ HTML_WARNINGS = ENV_WARNINGS + """\ @@ -662,7 +659,7 @@ def test_numfig_without_numbered_toctree(app, status, warning): yield check_xpath, etree, fname, xpath, check, be_found -@gen_with_app(buildername='html', testroot='numfig', +@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() @@ -763,6 +760,7 @@ def test_numfig_with_numbered_toctree(app, status, warning): @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', @@ -867,6 +865,7 @@ def test_numfig_with_prefix(app, status, warning): @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() @@ -967,6 +966,7 @@ def test_numfig_with_secnum_depth(app, status, warning): @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() diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index dfcadc6a1..7366763ca 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -12,11 +12,11 @@ from __future__ import print_function import os import re -from functools import wraps from itertools import product from subprocess import Popen, PIPE from six import PY3 +import pytest from sphinx.errors import SphinxError from sphinx.util.osutil import cd, ensuredir @@ -90,14 +90,13 @@ def skip_if_stylefiles_notfound(testfunc): return testfunc -def test_latex(): - for engine, docclass in product(LATEX_ENGINES, DOCCLASSES): - yield build_latex_doc, engine, docclass - - @skip_if_stylefiles_notfound -@with_app(buildername='latex') -def build_latex_doc(app, status, warning, engine, docclass): +@pytest.mark.parametrize( + "engine,docclass", + product(LATEX_ENGINES, DOCCLASSES), +) +@pytest.mark.sphinx('latex') +def test_build_latex_doc(app, status, warning, engine, docclass): app.config.latex_engine = engine app.config.latex_documents[0] = app.config.latex_documents[0][:4] + (docclass,) diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index ca6608490..d0606d945 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -10,7 +10,7 @@ """ import shutil -from nose.tools import with_setup +import pytest from util import with_app, find_files, rootdir, tempdir @@ -19,6 +19,7 @@ build_dir = root / '_build' locale_dir = build_dir / 'locale' +@pytest.fixture def setup_test(): # delete remnants left over after failed build root.rmtree(True) @@ -30,12 +31,12 @@ def setup_test(): copy_po.parent.makedirs() shutil.copy(root / po, copy_po) + yield -def teardown_test(): build_dir.rmtree(True) -@with_setup(setup_test, teardown_test) +@pytest.mark.usefixtures('setup_test') @with_app(buildername='html', testroot='intl', confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) def test_compile_all_catalogs(app, status, warning): @@ -51,7 +52,7 @@ def test_compile_all_catalogs(app, status, warning): assert actual == expect -@with_setup(setup_test, teardown_test) +@pytest.mark.usefixtures('setup_test') @with_app(buildername='html', testroot='intl', confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) def test_compile_specific_catalogs(app, status, warning): @@ -66,7 +67,7 @@ def test_compile_specific_catalogs(app, status, warning): assert actual == set(['admonitions.mo']) -@with_setup(setup_test, teardown_test) +@pytest.mark.usefixtures('setup_test') @with_app(buildername='html', testroot='intl', confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) def test_compile_update_catalogs(app, status, warning): diff --git a/tests/test_config.py b/tests/test_config.py index 1b3c94957..9464dfd0d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -87,16 +87,16 @@ def test_extension_values(app, status, warning): @with_tempdir -def test_errors_warnings(dir): +def test_errors_warnings(tempdir): # test the error for syntax errors in the config file - (dir / 'conf.py').write_text(u'project = \n', encoding='ascii') - raises_msg(ConfigError, 'conf.py', Config, dir, 'conf.py', {}, None) + (tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii') + raises_msg(ConfigError, 'conf.py', Config, tempdir, 'conf.py', {}, None) # test the automatic conversion of 2.x only code in configs - (dir / 'conf.py').write_text( + (tempdir / 'conf.py').write_text( u'# -*- coding: utf-8\n\nproject = u"Jägermeister"\n', encoding='utf-8') - cfg = Config(dir, 'conf.py', {}, None) + cfg = Config(tempdir, 'conf.py', {}, None) cfg.init_values(lambda warning: 1/0) assert cfg.project == u'Jägermeister' @@ -105,9 +105,9 @@ def test_errors_warnings(dir): # skip the test there if PY3: return - (dir / 'conf.py').write_text( + (tempdir / 'conf.py').write_text( u'# -*- coding: latin-1\nproject = "fooä"\n', encoding='latin-1') - cfg = Config(dir, 'conf.py', {}, None) + cfg = Config(tempdir, 'conf.py', {}, None) warned = [False] def warn(msg): @@ -118,10 +118,10 @@ def test_errors_warnings(dir): @with_tempdir -def test_errors_if_setup_is_not_callable(dir): +def test_errors_if_setup_is_not_callable(tempdir): # test the error to call setup() in the config file - (dir / 'conf.py').write_text(u'setup = 1') - raises_msg(ConfigError, 'callable', TestApp, srcdir=dir) + (tempdir / 'conf.py').write_text(u'setup = 1') + raises_msg(ConfigError, 'callable', TestApp, srcdir=tempdir) @mock.patch.object(sphinx, '__display_version__', '1.3.4') @@ -152,12 +152,12 @@ def test_needs_sphinx(): @with_tempdir -def test_config_eol(tmpdir): +def test_config_eol(tempdir): # test config file's eol patterns: LF, CRLF - configfile = tmpdir / 'conf.py' + configfile = tempdir / 'conf.py' for eol in (b'\n', b'\r\n'): configfile.write_bytes(b'project = "spam"' + eol) - cfg = Config(tmpdir, 'conf.py', {}, None) + cfg = Config(tempdir, 'conf.py', {}, None) cfg.init_values(lambda warning: 1/0) assert cfg.project == u'spam' diff --git a/tests/test_environment.py b/tests/test_environment.py index f65a6f3f0..232b6e28b 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -54,7 +54,6 @@ def test_images(): 'http://www.python.org/logo.png') tree = env.get_doctree('images') - app._warning.reset() htmlbuilder = StandaloneHTMLBuilder(app) htmlbuilder.imgpath = 'dummy' htmlbuilder.post_process_images(tree) @@ -64,7 +63,6 @@ def test_images(): assert set(htmlbuilder.images.values()) == \ set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg', 'img.foo.png']) - app._warning.reset() latexbuilder = LaTeXBuilder(app) latexbuilder.post_process_images(tree) assert set(latexbuilder.images.keys()) == \ diff --git a/tests/test_ext_graphviz.py b/tests/test_ext_graphviz.py index 7d464343f..e59143d37 100644 --- a/tests/test_ext_graphviz.py +++ b/tests/test_ext_graphviz.py @@ -10,37 +10,14 @@ """ import re -import subprocess -from functools import wraps + +import pytest from util import with_app, SkipTest -def skip_if_graphviz_not_found(fn): - @wraps(fn) - def decorator(app, *args, **kwargs): - found = False - graphviz_dot = getattr(app.config, 'graphviz_dot', '') - try: - if graphviz_dot: - dot = subprocess.Popen([graphviz_dot, '-V'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) # show version - dot.communicate() - found = True - except OSError: # No such file or directory - pass - - if not found: - raise SkipTest('graphviz "dot" is not available') - - return fn(app, *args, **kwargs) - - return decorator - - @with_app('html', testroot='ext-graphviz') -@skip_if_graphviz_not_found +@pytest.mark.usefixtures('if_graphviz_found') def test_graphviz_html(app, status, warning): app.builder.build_all() @@ -61,7 +38,7 @@ def test_graphviz_html(app, status, warning): @with_app('latex', testroot='ext-graphviz') -@skip_if_graphviz_not_found +@pytest.mark.usefixtures('if_graphviz_found') def test_graphviz_latex(app, status, warning): app.builder.build_all() @@ -81,7 +58,7 @@ def test_graphviz_latex(app, status, warning): @with_app('html', testroot='ext-graphviz', confoverrides={'language': 'xx'}) -@skip_if_graphviz_not_found +@pytest.mark.usefixtures('if_graphviz_found') def test_graphviz_i18n(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index 0171cafe6..fb1d127d9 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -12,12 +12,12 @@ import re import sys from util import with_app, rootdir, raises -from test_ext_graphviz import skip_if_graphviz_not_found from sphinx.ext.inheritance_diagram import InheritanceException, import_classes +import pytest @with_app('html', testroot='ext-inheritance_diagram') -@skip_if_graphviz_not_found +@pytest.mark.usefixtures('if_graphviz_found') def test_inheritance_diagram_html(app, status, warning): app.builder.build_all() @@ -32,7 +32,7 @@ def test_inheritance_diagram_html(app, status, warning): @with_app('latex', testroot='ext-inheritance_diagram') -@skip_if_graphviz_not_found +@pytest.mark.usefixtures('if_graphviz_found') def test_inheritance_diagram_latex(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index e1995e3d3..8de0e2b1e 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -85,7 +85,7 @@ def test_read_inventory_v2(): @with_app() @mock.patch('sphinx.ext.intersphinx.read_inventory') @mock.patch('sphinx.ext.intersphinx._read_from_url') -def test_fetch_inventory_redirection(app, status, warning, _read_from_url, read_inventory): +def test_fetch_inventory_redirection(_read_from_url, read_inventory, app, status, warning): intersphinx_setup(app) _read_from_url().readline.return_value = '# Sphinx inventory version 2'.encode('utf-8') diff --git a/tests/test_intl.py b/tests/test_intl.py index 21d6f763b..f92f44cf4 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -18,12 +18,11 @@ from docutils import nodes from subprocess import Popen, PIPE from babel.messages import pofile -from nose.tools import assert_equal from six import string_types from util import tempdir, rootdir, path, gen_with_app, with_app, SkipTest, \ assert_re_search, assert_not_re_search, assert_in, assert_not_in, \ - assert_startswith, assert_node, repr_as, etree_parse + assert_startswith, assert_node, repr_as, etree_parse, assert_equal root = tempdir / 'test-intl' diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 14ab51ccd..2bb1d746c 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -14,8 +14,6 @@ from util import with_app -from nose.tools import assert_equal - @with_app('pseudoxml') def test_docinfo(app, status, warning): @@ -53,8 +51,4 @@ def test_docinfo(app, status, warning): 'orphan': u'', 'nocomments': u'', } - # I like this way of comparing dicts - easier to see the error. - for key in exampledocinfo: - yield assert_equal, exampledocinfo.get(key), expecteddocinfo.get(key) - # but then we still have to check for missing keys - yield assert_equal, set(expecteddocinfo.keys()), set(exampledocinfo.keys()) + assert exampledocinfo == expecteddocinfo diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py index c92f6220f..c7874eb25 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -12,12 +12,13 @@ import os import sys import subprocess -from functools import wraps -import tempfile +from collections import namedtuple import sphinx -from util import rootdir, tempdir, SkipTest -from path import path +import pytest + +from sphinx.util.osutil import cd +from util import rootdir, tempdir from textwrap import dedent root = tempdir / 'test-setup' @@ -28,57 +29,52 @@ def setup_module(): (rootdir / 'roots' / 'test-setup').copytree(root) -def with_setup_command(root, *args, **kwds): +@pytest.fixture +def setup_command(request, tempdir): """ Run `setup.py build_sphinx` with args and kwargs, pass it to the test and clean up properly. """ - def generator(func): - @wraps(func) - def deco(*args2, **kwargs2): - tempdir = path(tempfile.mkdtemp()) - pkgrootdir = (tempdir / 'root') - root.copytree(pkgrootdir) - cwd = os.getcwd() - os.chdir(pkgrootdir) - pythonpath = os.path.dirname(os.path.dirname(sphinx.__file__)) - if os.getenv('PYTHONPATH'): - pythonpath = os.getenv('PYTHONPATH') + os.pathsep + pythonpath - command = [sys.executable, 'setup.py', 'build_sphinx'] - command.extend(args) - try: - proc = subprocess.Popen( - command, - env=dict(os.environ, PYTHONPATH=pythonpath), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - func(pkgrootdir, proc) - finally: - tempdir.rmtree(ignore_errors=True) - os.chdir(cwd) - return deco - return generator + marker = request.node.get_marker('setup_command') + args = marker.args if marker else [] + + pkgrootdir = tempdir / 'root' + root.copytree(pkgrootdir) + + with cd(pkgrootdir): + pythonpath = os.path.dirname(os.path.dirname(sphinx.__file__)) + if os.getenv('PYTHONPATH'): + pythonpath = os.getenv('PYTHONPATH') + os.pathsep + pythonpath + command = [sys.executable, 'setup.py', 'build_sphinx'] + command.extend(args) + + proc = subprocess.Popen( + command, + env=dict(os.environ, PYTHONPATH=pythonpath), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + yield namedtuple('setup', 'pkgroot,proc')(pkgrootdir, proc) -@with_setup_command(root) -def test_build_sphinx(pkgroot, proc): +def test_build_sphinx(setup_command): + proc = setup_command.proc out, err = proc.communicate() print(out) print(err) assert proc.returncode == 0 -@with_setup_command(root) -def test_build_sphinx_with_nonascii_path(pkgroot, proc): +@pytest.fixture +def nonascii_srcdir(request, setup_command): mb_name = u'\u65e5\u672c\u8a9e' - srcdir = (pkgroot / 'doc') + srcdir = (setup_command.pkgroot / 'doc') try: (srcdir / mb_name).makedirs() except UnicodeEncodeError: from path import FILESYSTEMENCODING - raise SkipTest( + pytest.skip( 'non-ASCII filename not supported on this filesystem encoding: ' - '%s', FILESYSTEMENCODING) + '%s' % FILESYSTEMENCODING) (srcdir / mb_name / (mb_name + '.txt')).write_text(dedent(""" multi byte file name page @@ -91,41 +87,46 @@ def test_build_sphinx_with_nonascii_path(pkgroot, proc): %(mb_name)s/%(mb_name)s """ % locals()) - ).encode('utf-8')) + ).encode('utf-8')) + +def test_build_sphinx_with_nonascii_path(setup_command, nonascii_srcdir): + proc = setup_command.proc out, err = proc.communicate() print(out) print(err) assert proc.returncode == 0 -@with_setup_command(root, '-b', 'linkcheck') -def test_build_sphinx_return_nonzero_status(pkgroot, proc): - srcdir = (pkgroot / 'doc') +@pytest.mark.setup_command('-b', 'linkcheck') +def test_build_sphinx_return_nonzero_status(setup_command): + srcdir = (setup_command.pkgroot / 'doc') (srcdir / 'contents.txt').write_text( 'http://localhost.unexistentdomain/index.html') + proc = setup_command.proc out, err = proc.communicate() print(out) print(err) assert proc.returncode != 0, 'expect non-zero status for setup.py' -@with_setup_command(root) -def test_build_sphinx_warning_return_zero_status(pkgroot, proc): - srcdir = (pkgroot / 'doc') +def test_build_sphinx_warning_return_zero_status(setup_command): + srcdir = (setup_command.pkgroot / 'doc') (srcdir / 'contents.txt').write_text( 'See :ref:`unexisting-reference-label`') + proc = setup_command.proc out, err = proc.communicate() print(out) print(err) assert proc.returncode == 0 -@with_setup_command(root, '--warning-is-error') -def test_build_sphinx_warning_is_error_return_nonzero_status(pkgroot, proc): - srcdir = (pkgroot / 'doc') +@pytest.mark.setup_command('--warning-is-error') +def test_build_sphinx_warning_is_error_return_nonzero_status(setup_command): + srcdir = (setup_command.pkgroot / 'doc') (srcdir / 'contents.txt').write_text( 'See :ref:`unexisting-reference-label`') + proc = setup_command.proc out, err = proc.communicate() print(out) print(err) diff --git a/tests/test_theming.py b/tests/test_theming.py index b62cbcd72..bb27f9677 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -83,7 +83,8 @@ def test_js_source(app, status, warning): assert 'Underscore.js {v}'.format(v=v) in underscore_src, msg -def test_double_inheriting_theme(): +@with_app(testroot='double-inheriting-theme') +def test_double_inheriting_theme(make_app, app_params): from sphinx.theming import load_theme_plugins # load original before patching def load_themes(): @@ -92,8 +93,6 @@ def test_double_inheriting_theme(): for t in load_theme_plugins(): yield t - @mock.patch('sphinx.theming.load_theme_plugins', side_effect=load_themes) - @with_app(testroot='double-inheriting-theme') - def test_double_inheriting_theme_(app, status, warning, m_): - pass - yield test_double_inheriting_theme_ + with mock.patch('sphinx.theming.load_theme_plugins', side_effect=load_themes): + args, kwargs = app_params + make_app(*args, **kwargs) diff --git a/tests/test_util_fileutil.py b/tests/test_util_fileutil.py index 5810dd2a8..ef0af07fc 100644 --- a/tests/test_util_fileutil.py +++ b/tests/test_util_fileutil.py @@ -25,32 +25,32 @@ class DummyTemplateLoader(BuiltinTemplateLoader): @with_tempdir -def test_copy_asset_file(tmpdir): +def test_copy_asset_file(tempdir): renderer = DummyTemplateLoader() # copy normal file - src = (tmpdir / 'asset.txt') + src = (tempdir / 'asset.txt') src.write_text('# test data') - dest = (tmpdir / 'output.txt') + dest = (tempdir / 'output.txt') copy_asset_file(src, dest) assert dest.exists() assert src.text() == dest.text() # copy template file - src = (tmpdir / 'asset.txt_t') + src = (tempdir / 'asset.txt_t') src.write_text('# {{var1}} data') - dest = (tmpdir / 'output.txt_t') + dest = (tempdir / 'output.txt_t') copy_asset_file(src, dest, {'var1': 'template'}, renderer) assert not dest.exists() - assert (tmpdir / 'output.txt').exists() - assert (tmpdir / 'output.txt').text() == '# template data' + assert (tempdir / 'output.txt').exists() + assert (tempdir / 'output.txt').text() == '# template data' # copy template file to subdir - src = (tmpdir / 'asset.txt_t') + src = (tempdir / 'asset.txt_t') src.write_text('# {{var1}} data') - subdir1 = (tmpdir / 'subdir') + subdir1 = (tempdir / 'subdir') subdir1.makedirs() copy_asset_file(src, subdir1, {'var1': 'template'}, renderer) @@ -58,8 +58,8 @@ def test_copy_asset_file(tmpdir): assert (subdir1 / 'asset.txt').text() == '# template data' # copy template file without context - src = (tmpdir / 'asset.txt_t') - subdir2 = (tmpdir / 'subdir2') + src = (tempdir / 'asset.txt_t') + subdir2 = (tempdir / 'subdir2') subdir2.makedirs() copy_asset_file(src, subdir2) @@ -69,11 +69,11 @@ def test_copy_asset_file(tmpdir): @with_tempdir -def test_copy_asset(tmpdir): +def test_copy_asset(tempdir): renderer = DummyTemplateLoader() # prepare source files - source = (tmpdir / 'source') + source = (tempdir / 'source') source.makedirs() (source / 'index.rst').write_text('index.rst') (source / 'foo.rst_t').write_text('{{var1}}.rst') @@ -84,13 +84,13 @@ def test_copy_asset(tmpdir): (source / '_templates' / 'sidebar.html_t').write_text('sidebar: {{var2}}') # copy a single file - assert not (tmpdir / 'test1').exists() - copy_asset(source / 'index.rst', tmpdir / 'test1') - assert (tmpdir / 'test1').exists() - assert (tmpdir / 'test1/index.rst').exists() + assert not (tempdir / 'test1').exists() + copy_asset(source / 'index.rst', tempdir / 'test1') + assert (tempdir / 'test1').exists() + assert (tempdir / 'test1/index.rst').exists() # copy directories - destdir = tmpdir / 'test2' + destdir = tempdir / 'test2' copy_asset(source, destdir, context=dict(var1='bar', var2='baz'), renderer=renderer) assert (destdir / 'index.rst').exists() assert (destdir / 'foo.rst').exists() @@ -104,7 +104,7 @@ def test_copy_asset(tmpdir): def excluded(path): return ('sidebar.html' in path or 'basic.css' in path) - destdir = tmpdir / 'test3' + destdir = tempdir / 'test3' copy_asset(source, destdir, excluded, context=dict(var1='bar', var2='baz'), renderer=renderer) assert (destdir / 'index.rst').exists() diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 849796a8f..8738862c4 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -38,12 +38,12 @@ def test_catalog_info_for_sub_domain_file_and_path(): @with_tempdir -def test_catalog_outdated(dir): - (dir / 'test.po').write_text('#') - cat = i18n.CatalogInfo(dir, 'test', 'utf-8') +def test_catalog_outdated(tempdir): + (tempdir / 'test.po').write_text('#') + cat = i18n.CatalogInfo(tempdir, 'test', 'utf-8') assert cat.is_outdated() # if mo is not exist - mo_file = (dir / 'test.mo') + mo_file = (tempdir / 'test.mo') mo_file.write_text('#') assert not cat.is_outdated() # if mo is exist and newer than po @@ -52,9 +52,9 @@ def test_catalog_outdated(dir): @with_tempdir -def test_catalog_write_mo(dir): - (dir / 'test.po').write_text('#') - cat = i18n.CatalogInfo(dir, 'test', 'utf-8') +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) with open(cat.mo_path, 'rb') as f: @@ -62,20 +62,20 @@ def test_catalog_write_mo(dir): @with_tempdir -def test_get_catalogs_for_xx(dir): - (dir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test3.pot').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.po').write_text('#') - (dir / 'loc1' / 'en' / 'LC_MESSAGES').makedirs() - (dir / 'loc1' / 'en' / 'LC_MESSAGES' / 'test6.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_ALL').makedirs() - (dir / 'loc1' / 'xx' / 'LC_ALL' / 'test7.po').write_text('#') +def test_get_catalogs_for_xx(tempdir): + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test3.pot').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.po').write_text('#') + (tempdir / 'loc1' / 'en' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc1' / 'en' / 'LC_MESSAGES' / 'test6.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_ALL').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_ALL' / 'test7.po').write_text('#') - catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', force_all=False) + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], 'xx', force_all=False) domains = set(c.domain for c in catalogs) assert domains == set([ 'test1', @@ -86,23 +86,23 @@ def test_get_catalogs_for_xx(dir): @with_tempdir -def test_get_catalogs_for_en(dir): - (dir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'xx_dom.po').write_text('#') - (dir / 'loc1' / 'en' / 'LC_MESSAGES').makedirs() - (dir / 'loc1' / 'en' / 'LC_MESSAGES' / 'en_dom.po').write_text('#') +def test_get_catalogs_for_en(tempdir): + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'xx_dom.po').write_text('#') + (tempdir / 'loc1' / 'en' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc1' / 'en' / 'LC_MESSAGES' / 'en_dom.po').write_text('#') - catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'en', force_all=False) + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], 'en', force_all=False) domains = set(c.domain for c in catalogs) assert domains == set(['en_dom']) @with_tempdir -def test_get_catalogs_with_non_existent_locale(dir): - catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx') +def test_get_catalogs_with_non_existent_locale(tempdir): + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], 'xx') assert not catalogs - catalogs = i18n.find_catalog_source_files([dir / 'loc1'], None) + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], None) assert not catalogs @@ -112,24 +112,24 @@ def test_get_catalogs_with_non_existent_locale_dirs(): @with_tempdir -def test_get_catalogs_for_xx_without_outdated(dir): - (dir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.mo').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.mo').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test3.pot').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test3.mo').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.mo').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.mo').write_text('#') +def test_get_catalogs_for_xx_without_outdated(tempdir): + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.mo').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.mo').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test3.pot').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test3.mo').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.mo').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.mo').write_text('#') - catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', force_all=False) + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], 'xx', force_all=False) assert not catalogs - catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', force_all=True) + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], 'xx', force_all=True) domains = set(c.domain for c in catalogs) assert domains == set([ 'test1', @@ -140,28 +140,28 @@ def test_get_catalogs_for_xx_without_outdated(dir): @with_tempdir -def test_get_catalogs_from_multiple_locale_dirs(dir): - (dir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') - (dir / 'loc2' / 'xx' / 'LC_MESSAGES').makedirs() - (dir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') - (dir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') +def test_get_catalogs_from_multiple_locale_dirs(tempdir): + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') + (tempdir / 'loc2' / 'xx' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') + (tempdir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') - catalogs = i18n.find_catalog_source_files([dir / 'loc1', dir / 'loc2'], 'xx') + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1', tempdir / 'loc2'], 'xx') domains = sorted(c.domain for c in catalogs) assert domains == ['test1', 'test1', 'test2'] @with_tempdir -def test_get_catalogs_with_compact(dir): - (dir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').makedirs() - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test3.po').write_text('#') - (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#') +def test_get_catalogs_with_compact(tempdir): + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').makedirs() + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test3.po').write_text('#') + (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#') - catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', gettext_compact=True) + catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], 'xx', gettext_compact=True) domains = set(c.domain for c in catalogs) assert domains == set(['test1', 'test2', 'sub']) diff --git a/tests/test_websupport.py b/tests/test_websupport.py index bb41ae1ab..af9ed91ee 100644 --- a/tests/test_websupport.py +++ b/tests/test_websupport.py @@ -9,10 +9,6 @@ :license: BSD, see LICENSE for details. """ -from functools import wraps - -from six import StringIO - from sphinx.websupport import WebSupport from sphinx.websupport.errors import DocumentNotFoundError, \ CommentNotAllowedError, UserNotAuthorizedError @@ -26,26 +22,28 @@ try: except ImportError: sqlalchemy_missing = True +import pytest from util import rootdir, tempdir, raises, skip_if -default_settings = {'builddir': tempdir / 'websupport', - 'status': StringIO(), - 'warning': StringIO()} +@pytest.fixture +def support(request): + settings = { + 'srcdir': rootdir / 'root', + # to use same directory for 'builddir' in each 'support' fixture, using + # 'tempdir' (static) value instead of 'tempdir' fixture value. + # each test expect result of db value at previous test case. + 'builddir': tempdir / 'websupport' + } + marker = request.node.get_marker('support') + if marker: + settings.update(marker.kwargs) + + support = WebSupport(**settings) + yield support -def with_support(*args, **kwargs): - """Make a WebSupport object and pass it the test.""" - settings = default_settings.copy() - settings.update(kwargs) - - def generator(func): - @wraps(func) - def new_func(*args2, **kwargs2): - support = WebSupport(**settings) - func(support, *args2, **kwargs2) - return new_func - return generator +with_support = pytest.mark.support class NullStorage(StorageBackend): @@ -59,7 +57,7 @@ def test_no_srcdir(support): @skip_if(sqlalchemy_missing, 'needs sqlalchemy') -@with_support(srcdir=rootdir / 'root') +@with_support() def test_build(support): support.build() @@ -123,56 +121,6 @@ def test_comments(support): assert children[0]['text'] == '

Child test comment

\n' -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') -@with_support() -def test_voting(support): - session = Session() - nodes = session.query(Node).all() - node = nodes[0] - - comment = support.get_data(node.id)['comments'][0] - - def check_rating(val): - data = support.get_data(node.id) - comment = data['comments'][0] - assert comment['rating'] == val, '%s != %s' % (comment['rating'], val) - - support.process_vote(comment['id'], 'user_one', '1') - support.process_vote(comment['id'], 'user_two', '1') - support.process_vote(comment['id'], 'user_three', '1') - check_rating(3) - support.process_vote(comment['id'], 'user_one', '-1') - check_rating(1) - support.process_vote(comment['id'], 'user_one', '0') - check_rating(2) - - # Make sure a vote with value > 1 or < -1 can't be cast. - raises(ValueError, support.process_vote, comment['id'], 'user_one', '2') - raises(ValueError, support.process_vote, comment['id'], 'user_one', '-2') - - # Make sure past voting data is associated with comments when they are - # fetched. - data = support.get_data(str(node.id), username='user_two') - comment = data['comments'][0] - assert comment['vote'] == 1, '%s != 1' % comment['vote'] - - -@skip_if(sqlalchemy_missing, 'needs sqlalchemy') -@with_support() -def test_proposals(support): - session = Session() - node = session.query(Node).first() - - data = support.get_data(node.id) - - source = data['source'] - proposal = source[:5] + source[10:15] + 'asdf' + source[15:] - - support.add_comment('Proposal comment', - node_id=node.id, - proposal=proposal) - - @skip_if(sqlalchemy_missing, 'needs sqlalchemy') @with_support() def test_user_delete_comments(support): @@ -194,6 +142,38 @@ def test_user_delete_comments(support): assert comment['text'] == '[deleted]' +called = False + + +def moderation_callback(comment): + global called + called = True + + +@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@with_support(moderation_callback=moderation_callback) +def test_moderation(support): + session = Session() + nodes = session.query(Node).all() + node = nodes[7] + session.close() + accepted = support.add_comment('Accepted Comment', node_id=node.id, + displayed=False) + deleted = support.add_comment('Comment to delete', node_id=node.id, + displayed=False) + # Make sure the moderation_callback is called. + assert called + # Make sure the user must be a moderator. + raises(UserNotAuthorizedError, support.accept_comment, accepted['id']) + raises(UserNotAuthorizedError, support.delete_comment, deleted['id']) + support.accept_comment(accepted['id'], moderator=True) + support.delete_comment(deleted['id'], moderator=True) + comments = support.get_data(node.id)['comments'] + assert len(comments) == 1 + comments = support.get_data(node.id, moderator=True)['comments'] + assert len(comments) == 1 + + @skip_if(sqlalchemy_missing, 'needs sqlalchemy') @with_support() def test_moderator_delete_comments(support): @@ -228,36 +208,54 @@ def test_update_username(support): assert len(votes) == 0 -called = False +@skip_if(sqlalchemy_missing, 'needs sqlalchemy') +@with_support() +def test_proposals(support): + session = Session() + node = session.query(Node).first() + data = support.get_data(node.id) -def moderation_callback(comment): - global called - called = True + source = data['source'] + proposal = source[:5] + source[10:15] + 'asdf' + source[15:] + + support.add_comment('Proposal comment', + node_id=node.id, + proposal=proposal) @skip_if(sqlalchemy_missing, 'needs sqlalchemy') -@with_support(moderation_callback=moderation_callback) -def test_moderation(support): +@with_support() +def test_voting(support): session = Session() nodes = session.query(Node).all() - node = nodes[7] - session.close() - accepted = support.add_comment('Accepted Comment', node_id=node.id, - displayed=False) - deleted = support.add_comment('Comment to delete', node_id=node.id, - displayed=False) - # Make sure the moderation_callback is called. - assert called - # Make sure the user must be a moderator. - raises(UserNotAuthorizedError, support.accept_comment, accepted['id']) - raises(UserNotAuthorizedError, support.delete_comment, deleted['id']) - support.accept_comment(accepted['id'], moderator=True) - support.delete_comment(deleted['id'], moderator=True) - comments = support.get_data(node.id)['comments'] - assert len(comments) == 1 - comments = support.get_data(node.id, moderator=True)['comments'] - assert len(comments) == 1 + node = nodes[0] + + comment = support.get_data(node.id)['comments'][0] + + def check_rating(val): + data = support.get_data(node.id) + comment = data['comments'][0] + assert comment['rating'] == val, '%s != %s' % (comment['rating'], val) + + support.process_vote(comment['id'], 'user_one', '1') + support.process_vote(comment['id'], 'user_two', '1') + support.process_vote(comment['id'], 'user_three', '1') + check_rating(3) + support.process_vote(comment['id'], 'user_one', '-1') + check_rating(1) + support.process_vote(comment['id'], 'user_one', '0') + check_rating(2) + + # Make sure a vote with value > 1 or < -1 can't be cast. + raises(ValueError, support.process_vote, comment['id'], 'user_one', '2') + raises(ValueError, support.process_vote, comment['id'], 'user_one', '-2') + + # Make sure past voting data is associated with comments when they are + # fetched. + data = support.get_data(str(node.id), username='user_two') + comment = data['comments'][0] + assert comment['vote'] == 1, '%s != 1' % comment['vote'] def test_differ(): diff --git a/tests/util.py b/tests/util.py index 13366a1da..de158cc7f 100644 --- a/tests/util.py +++ b/tests/util.py @@ -10,14 +10,13 @@ import os import re import sys -import tempfile import warnings from functools import wraps from xml.etree import ElementTree -from six import StringIO, string_types +from six import string_types -from nose import tools, SkipTest +import pytest from docutils import nodes from docutils.parsers.rst import directives, roles @@ -27,16 +26,17 @@ from sphinx.builders.latex import LaTeXBuilder from sphinx.theming import Theme from sphinx.ext.autodoc import AutoDirective from sphinx.pycode import ModuleAnalyzer +from sphinx.deprecation import RemovedInSphinx17Warning from path import path, repr_as # NOQA __all__ = [ - 'rootdir', 'tempdir', 'raises', 'raises_msg', - 'skip_if', 'skip_unless', 'skip_unless_importable', 'Struct', - 'ListOutput', 'TestApp', 'with_app', 'gen_with_app', - 'path', 'with_tempdir', - 'sprint', 'remove_unicode_literals', + 'rootdir', 'tempdir', + 'skip_unless_importable', 'Struct', + 'SphinxTestApp', + 'path', + 'remove_unicode_literals', ] @@ -44,36 +44,6 @@ rootdir = path(os.path.dirname(__file__) or '.').abspath() tempdir = path(os.environ['SPHINX_TEST_TEMPDIR']).abspath() -def _excstr(exc): - if type(exc) is tuple: - return str(tuple(map(_excstr, exc))) - return exc.__name__ - - -def raises(exc, func, *args, **kwds): - """Raise AssertionError if ``func(*args, **kwds)`` does not raise *exc*.""" - try: - func(*args, **kwds) - except exc: - pass - else: - raise AssertionError('%s did not raise %s' % - (func.__name__, _excstr(exc))) - - -def raises_msg(exc, msg, func, *args, **kwds): - """Raise AssertionError if ``func(*args, **kwds)`` does not raise *exc*, - and check if the message contains *msg*. - """ - try: - func(*args, **kwds) - except exc as err: - assert msg in str(err), "\"%s\" not in \"%s\"" % (msg, err) - else: - raise AssertionError('%s did not raise %s' % - (func.__name__, _excstr(exc))) - - def assert_re_search(regex, text, flags=0): if not re.search(regex, text, flags): assert False, '%r did not match %r' % (regex, text) @@ -118,43 +88,14 @@ def assert_node(node, cls=None, xpath="", **kwargs): 'The node%s[%s] is not %r: %r' % (xpath, key, value, node[key]) -try: - from nose.tools import assert_in, assert_not_in -except ImportError: - def assert_in(x, thing, msg=''): - if x not in thing: - assert False, msg or '%r is not in %r' % (x, thing) - - def assert_not_in(x, thing, msg=''): - if x in thing: - assert False, msg or '%r is in %r' % (x, thing) - - -def skip_if(condition, msg=None): - """Decorator to skip test if condition is true.""" - def deco(test): - @tools.make_decorator(test) - def skipper(*args, **kwds): - if condition: - raise SkipTest(msg or 'conditional skip') - return test(*args, **kwds) - return skipper - return deco - - -def skip_unless(condition, msg=None): - """Decorator to skip test if condition is false.""" - return skip_if(not condition, msg) - - def skip_unless_importable(module, msg=None): """Decorator to skip test if module is not importable.""" try: __import__(module) except ImportError: - return skip_if(True, msg) + return pytest.mark.skipif(True, reason=(msg or 'conditional skip')) else: - return skip_if(False, msg) + return pytest.mark.skipif(False, reason=(msg or 'conditional skip')) def etree_parse(path): @@ -168,22 +109,7 @@ class Struct(object): self.__dict__.update(kwds) -class ListOutput(object): - """ - File-like object that collects written text in a list. - """ - def __init__(self, name): - self.name = name - self.content = [] - - def reset(self): - del self.content[:] - - def write(self, text): - self.content.append(text) - - -class TestApp(application.Sphinx): +class SphinxTestApp(application.Sphinx): """ A subclass of :class:`Sphinx` that runs on the test root, with some better default values for the initialization parameters. @@ -222,10 +148,6 @@ class TestApp(application.Sphinx): doctreedir.makedirs() if confoverrides is None: confoverrides = {} - if status is None: - status = StringIO() - if warning is None: - warning = ListOutput('stderr') # if warningiserror is None: warningiserror = False @@ -263,59 +185,6 @@ class TestApp(application.Sphinx): return '<%s buildername=%r>' % (self.__class__.__name__, self.builder.name) -def with_app(*args, **kwargs): - """ - Make a TestApp with args and kwargs, pass it to the test and clean up - properly. - """ - def generator(func): - @wraps(func) - def deco(*args2, **kwargs2): - status, warning = StringIO(), StringIO() - kwargs['status'] = status - kwargs['warning'] = warning - app = TestApp(*args, **kwargs) - try: - func(app, status, warning, *args2, **kwargs2) - finally: - app.cleanup() - return deco - return generator - - -def gen_with_app(*args, **kwargs): - """ - Decorate a test generator to pass a TestApp as the first argument to the - test generator when it's executed. - """ - def generator(func): - @wraps(func) - def deco(*args2, **kwargs2): - status, warning = StringIO(), StringIO() - kwargs['status'] = status - kwargs['warning'] = warning - app = TestApp(*args, **kwargs) - try: - for item in func(app, status, warning, *args2, **kwargs2): - yield item - finally: - app.cleanup() - return deco - return generator - - -def with_tempdir(func): - def new_func(*args, **kwds): - new_tempdir = path(tempfile.mkdtemp(dir=tempdir)) - func(new_tempdir, *args, **kwds) - new_func.__name__ = func.__name__ - return new_func - - -def sprint(*args): - sys.stderr.write(' '.join(map(str, args)) + '\n') - - _unicode_literals_re = re.compile(r'u(".*?")|u(\'.*?\')') @@ -333,3 +202,163 @@ def find_files(root, suffix=None): def strip_escseq(text): return re.sub('\x1b.*?m', '', text) + + +# ############################################# +# DEPRECATED implementations + +import tempfile +from six import StringIO + + +def gen_with_app(*args, **kwargs): + """ + **DEPRECATED**: use pytest.mark.parametrize instead. + + Decorate a test generator to pass a SphinxTestApp as the first argument to + the test generator when it's executed. + """ + def generator(func): + @wraps(func) + def deco(*args2, **kwargs2): + status, warning = StringIO(), StringIO() + kwargs['status'] = status + kwargs['warning'] = warning + app = SphinxTestApp(*args, **kwargs) + try: + for item in func(app, status, warning, *args2, **kwargs2): + yield item + finally: + app.cleanup() + return deco + return generator + + +def skip_if(condition, msg=None): + """ + **DEPRECATED**: use pytest.mark.skipif instead. + + Decorator to skip test if condition is true. + """ + return pytest.mark.skipif(condition, reason=(msg or 'conditional skip')) + + +def skip_unless(condition, msg=None): + """ + **DEPRECATED**: use pytest.mark.skipif instead. + + Decorator to skip test if condition is false. + """ + return pytest.mark.skipif(not condition, reason=(msg or 'conditional skip')) + + +def with_tempdir(func): + """ + **DEPRECATED**: use tempdir fixture instead. + """ + return func + + +def raises(exc, func, *args, **kwds): + """ + **DEPRECATED**: use pytest.raises instead. + + Raise AssertionError if ``func(*args, **kwds)`` does not raise *exc*. + """ + with pytest.raises(exc): + func(*args, **kwds) + + +def raises_msg(exc, msg, func, *args, **kwds): + """ + **DEPRECATED**: use pytest.raises instead. + + Raise AssertionError if ``func(*args, **kwds)`` does not raise *exc*, + and check if the message contains *msg*. + """ + with pytest.raises(exc) as excinfo: + func(*args, **kwds) + assert msg in str(excinfo.value) + + +def assert_true(v1, msg=''): + """ + **DEPRECATED**: use assert instead. + """ + assert v1, msg + + +def assert_equal(v1, v2, msg=''): + """ + **DEPRECATED**: use assert instead. + """ + assert v1 == v2, msg + + +def assert_in(x, thing, msg=''): + """ + **DEPRECATED**: use assert instead. + """ + if x not in thing: + assert False, msg or '%r is not in %r' % (x, thing) + + +def assert_not_in(x, thing, msg=''): + """ + **DEPRECATED**: use assert instead. + """ + if x in thing: + assert False, msg or '%r is in %r' % (x, thing) + + +class ListOutput(object): + """ + File-like object that collects written text in a list. + """ + def __init__(self, name): + self.name = name + self.content = [] + + def reset(self): + del self.content[:] + + def write(self, text): + self.content.append(text) + + +# **DEPRECATED**: use pytest.skip instead. +SkipTest = pytest.skip.Exception + + +class _DeprecationWrapper(object): + def __init__(self, mod, deprecated): + self._mod = mod + self._deprecated = deprecated + + def __getattr__(self, attr): + if attr in self._deprecated: + obj, instead = self._deprecated[attr] + warnings.warn("tests/util.py::%s is deprecated and will be " + "removed in Sphinx 1.7, please use %s instead." + % (attr, instead), + RemovedInSphinx17Warning, stacklevel=2) + return obj + return getattr(self._mod, attr) + + +sys.modules[__name__] = _DeprecationWrapper(sys.modules[__name__], dict( # type: ignore + with_app=(pytest.mark.sphinx, 'pytest.mark.sphinx'), + TestApp=(SphinxTestApp, 'SphinxTestApp'), + gen_with_app=(gen_with_app, 'pytest.mark.parametrize'), + skip_if=(skip_if, 'pytest.skipif'), + skip_unless=(skip_unless, 'pytest.skipif'), + with_tempdir=(with_tempdir, 'tmpdir pytest fixture'), + raises=(raises, 'pytest.raises'), + raises_msg=(raises_msg, 'pytest.raises'), + assert_true=(assert_true, 'assert'), + assert_equal=(assert_equal, 'assert'), + assert_in=(assert_in, 'assert'), + assert_not_in=(assert_not_in, 'assert'), + ListOutput=(ListOutput, 'StringIO'), + SkipTest=(SkipTest, 'pytest.skip'), +)) From ad6d731ecf780457bd3d6d2cf59832c083d63bfc Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Thu, 5 Jan 2017 23:36:36 +0900 Subject: [PATCH 02/38] pytest: remove unused testing feature for a while. --- tests/conftest.py | 127 +--------------------------------------------- 1 file changed, 2 insertions(+), 125 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9a021169e..1c5be83f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import sys import subprocess import pytest -from six import StringIO, string_types +from six import StringIO from util import SphinxTestApp, path @@ -29,75 +29,15 @@ def app_params(request): return args, kwargs -@pytest.fixture -def test_params(request): - """ - test parameters that is specified by 'pytest.mark.testenv' - - :param bool build: - If True, 'app' fixture will be build before test function is called. - Default is False. - :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('testenv') - kwargs = env.kwargs if env else {} - result = { - 'build': False, # pre build in fixture - 'specific_srcdir': None, - 'shared_result': None, - } - result.update(kwargs) - - if (result['shared_result'] and - not isinstance(result['shared_result'], string_types)): - r = 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 - - @pytest.fixture(scope='function') -def app(test_params, app_params, make_app, shared_result): +def app(app_params, make_app): """ provides sphinx.application.Sphinx object """ args, kwargs = app_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) - app_ = make_app(*args, **kwargs) - - if test_params['build']: - # if listdir is not empty, we can use built cache - if not app_.outdir.listdir(): - app_.build() yield app_ - if test_params['shared_result']: - shared_result.store(test_params['shared_result'], app_) - @pytest.fixture(scope='function') def status(app): @@ -139,38 +79,6 @@ 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): """ @@ -198,34 +106,3 @@ def tempdir(tmpdir): this fixture is for compat with old test implementation. """ return path(tmpdir) - - -def pytest_addoption(parser): - """ - the test that have pytest.mark.env('foobar') will be skipped when - '-S foobar' command-line option is provided. - """ - parser.addoption("-S", action="store", metavar="NAME", - help="skip tests matching the environment NAME.") - - -def pytest_configure(config): - """ - the test that have pytest.mark.env('foobar') will be skipped when - '-S foobar' command-line option is provided. - """ - # register an additional marker - config.addinivalue_line("markers", - "env(name): mark test to run only on named environment") - - -def pytest_runtest_setup(item): - """ - the test that have pytest.mark.env('foobar') will be skipped when - '-S foobar' command-line option is provided. - """ - envmarker = item.get_marker("env") - if envmarker is not None: - envname = envmarker.args[0] - if envname == item.config.getoption("-S"): - pytest.skip("skip test %r" % envname) From 380dd23d9f1a1263a92076e2c2daa97663fa6d5c Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Thu, 5 Jan 2017 23:43:16 +0900 Subject: [PATCH 03/38] pytest: remove repr_as testing feature that avoids long annoying log for test generator feature on nose. In contrast, pytest does not produce such output. So repr_as is not needed anymore. refs #1785 --- tests/path.py | 24 ++---------------------- tests/test_intl.py | 4 ++-- tests/util.py | 2 +- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/tests/path.py b/tests/path.py index 0d6892776..3c574f0c8 100755 --- a/tests/path.py +++ b/tests/path.py @@ -12,7 +12,7 @@ import sys import shutil from io import open -from six import PY2, text_type, binary_type +from six import PY2, text_type FILESYSTEMENCODING = sys.getfilesystemencoding() or sys.getdefaultencoding() @@ -144,9 +144,7 @@ class path(text_type): """ mode = 'rU' if PY2 else 'r' with open(self, mode=mode, encoding=encoding, **kwargs) as f: - text = f.read() - contents = repr_as(text, '<%s contents: %r>' % (self.basename(), text)) - return contents + return f.read() def bytes(self): """ @@ -201,21 +199,3 @@ class path(text_type): def __repr__(self): return '%s(%s)' % (self.__class__.__name__, text_type.__repr__(self)) - - -# Lives here only to avoid circular references; use it from util.py! -class _repr_text(text_type): - def __repr__(self): - return self._repr - - -class _repr_bin(binary_type): - def __repr__(self): - return self._repr - - -def repr_as(string, repr_): - wrapper = _repr_text if isinstance(string, text_type) else _repr_bin - proxy = wrapper(string) - proxy._repr = repr_ - return proxy diff --git a/tests/test_intl.py b/tests/test_intl.py index f92f44cf4..9fe5a21eb 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -22,7 +22,7 @@ from six import string_types from util import tempdir, rootdir, path, gen_with_app, with_app, SkipTest, \ assert_re_search, assert_not_re_search, assert_in, assert_not_in, \ - assert_startswith, assert_node, repr_as, etree_parse, assert_equal + assert_startswith, assert_node, etree_parse, assert_equal root = tempdir / 'test-intl' @@ -930,4 +930,4 @@ def test_image_glob_intl_using_figure_language_filename(app, status, warning): def getwarning(warnings): - return repr_as(warnings.getvalue().replace(os.sep, '/'), '') + return warnings.getvalue().replace(os.sep, '/') diff --git a/tests/util.py b/tests/util.py index de158cc7f..8f2d1ff9a 100644 --- a/tests/util.py +++ b/tests/util.py @@ -28,7 +28,7 @@ from sphinx.ext.autodoc import AutoDirective from sphinx.pycode import ModuleAnalyzer from sphinx.deprecation import RemovedInSphinx17Warning -from path import path, repr_as # NOQA +from path import path __all__ = [ From d0a33dd7852b306ed7471dd655921f3380da84df Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Fri, 6 Jan 2017 00:26:01 +0900 Subject: [PATCH 04/38] pytest: remove deprecated with_tempdir decorator --- tests/test_build.py | 3 +-- tests/test_config.py | 5 +---- tests/test_ext_intersphinx.py | 4 +--- tests/test_quickstart.py | 8 +------- tests/test_util_fileutil.py | 3 --- tests/test_util_i18n.py | 10 +--------- 6 files changed, 5 insertions(+), 28 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index 9f8db6b86..c99d87312 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -18,7 +18,7 @@ from textwrap import dedent from sphinx.errors import SphinxError import sphinx.builders.linkcheck -from util import with_app, with_tempdir, rootdir, tempdir, SkipTest, TestApp, path +from util import with_app, rootdir, tempdir, SkipTest, TestApp, path try: from docutils.writers.manpage import Writer as ManWriter @@ -77,7 +77,6 @@ def test_build_all(): yield verify_build, buildername, srcdir -@with_tempdir def test_master_doc_not_found(tempdir): (tempdir / 'conf.py').write_text('master_doc = "index"') assert tempdir.listdir() == ['conf.py'] diff --git a/tests/test_config.py b/tests/test_config.py index 9464dfd0d..2139c334e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -12,7 +12,7 @@ from six import PY3, iteritems import mock -from util import TestApp, with_app, gen_with_app, with_tempdir, \ +from util import TestApp, with_app, gen_with_app, \ raises, raises_msg, assert_in, assert_not_in import sphinx @@ -86,7 +86,6 @@ def test_extension_values(app, status, warning): 'value_from_ext', 'x', True) -@with_tempdir def test_errors_warnings(tempdir): # test the error for syntax errors in the config file (tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii') @@ -117,7 +116,6 @@ def test_errors_warnings(tempdir): assert warned[0] -@with_tempdir def test_errors_if_setup_is_not_callable(tempdir): # test the error to call setup() in the config file (tempdir / 'conf.py').write_text(u'setup = 1') @@ -151,7 +149,6 @@ def test_needs_sphinx(): confoverrides={'needs_sphinx': '2'}) # NG: greater -@with_tempdir def test_config_eol(tempdir): # test config file's eol patterns: LF, CRLF configfile = tempdir / 'conf.py' diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index 8de0e2b1e..92a9b8d33 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -23,7 +23,7 @@ from sphinx.ext.intersphinx import read_inventory, \ load_mappings, missing_reference, _strip_basic_auth, _read_from_url, \ _get_safe_url, fetch_inventory, INVENTORY_FILENAME -from util import with_app, with_tempdir +from util import with_app inventory_v1 = '''\ @@ -128,7 +128,6 @@ def test_fetch_inventory_redirection(_read_from_url, read_inventory, app, status @with_app() -@with_tempdir def test_missing_reference(tempdir, app, status, warning): inv_file = tempdir / 'inventory' inv_file.write_bytes(inventory_v2) @@ -219,7 +218,6 @@ def test_missing_reference(tempdir, app, status, warning): @with_app() -@with_tempdir def test_load_mappings_warnings(tempdir, app, status, warning): """ load_mappings issues a warning if new-style mapping diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 7a77ce225..80d9c60b6 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -15,7 +15,7 @@ import time from six import PY2, text_type, StringIO from six.moves import input -from util import raises, with_tempdir, SkipTest +from util import raises, SkipTest from sphinx import application from sphinx import quickstart as qs @@ -125,7 +125,6 @@ def test_do_prompt_with_nonascii(): assert d['k1'] == u'\u30c9\u30a4\u30c4' -@with_tempdir def test_quickstart_defaults(tempdir): answers = { 'Root path': tempdir, @@ -163,7 +162,6 @@ def test_quickstart_defaults(tempdir): assert (tempdir / 'make.bat').isfile() -@with_tempdir def test_quickstart_all_answers(tempdir): answers = { 'Root path': tempdir, @@ -231,7 +229,6 @@ def test_quickstart_all_answers(tempdir): assert (tempdir / 'source' / 'contents.txt').isfile() -@with_tempdir def test_generated_files_eol(tempdir): answers = { 'Root path': tempdir, @@ -252,7 +249,6 @@ def test_generated_files_eol(tempdir): assert_eol(tempdir / 'Makefile', '\n') -@with_tempdir def test_quickstart_and_build(tempdir): answers = { 'Root path': tempdir, @@ -278,7 +274,6 @@ def test_quickstart_and_build(tempdir): assert not warnings -@with_tempdir def test_default_filename(tempdir): answers = { 'Root path': tempdir, @@ -300,7 +295,6 @@ def test_default_filename(tempdir): assert ns['texinfo_documents'][0][1] == 'sphinx' -@with_tempdir def test_extensions(tempdir): qs.main(['sphinx-quickstart', '-q', '-p', 'project_name', '-a', 'author', diff --git a/tests/test_util_fileutil.py b/tests/test_util_fileutil.py index ef0af07fc..121b0d69f 100644 --- a/tests/test_util_fileutil.py +++ b/tests/test_util_fileutil.py @@ -12,7 +12,6 @@ from sphinx.util.fileutil import copy_asset, copy_asset_file from sphinx.jinja2glue import BuiltinTemplateLoader import mock -from util import with_tempdir class DummyTemplateLoader(BuiltinTemplateLoader): @@ -24,7 +23,6 @@ class DummyTemplateLoader(BuiltinTemplateLoader): self.init(builder) -@with_tempdir def test_copy_asset_file(tempdir): renderer = DummyTemplateLoader() @@ -68,7 +66,6 @@ def test_copy_asset_file(tempdir): assert (subdir2 / 'asset.txt_t').text() == '# {{var1}} data' -@with_tempdir def test_copy_asset(tempdir): renderer = DummyTemplateLoader() diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 8738862c4..38bfddea4 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -18,7 +18,7 @@ from babel.messages.mofile import read_mo from sphinx.util import i18n from sphinx.errors import SphinxError -from util import TestApp, with_tempdir, raises +from util import TestApp, raises def test_catalog_info_for_file_and_path(): @@ -37,7 +37,6 @@ def test_catalog_info_for_sub_domain_file_and_path(): assert cat.mo_path == path.join('path', 'sub/domain.mo') -@with_tempdir def test_catalog_outdated(tempdir): (tempdir / 'test.po').write_text('#') cat = i18n.CatalogInfo(tempdir, 'test', 'utf-8') @@ -51,7 +50,6 @@ def test_catalog_outdated(tempdir): assert cat.is_outdated() # if mo is exist and older than po -@with_tempdir def test_catalog_write_mo(tempdir): (tempdir / 'test.po').write_text('#') cat = i18n.CatalogInfo(tempdir, 'test', 'utf-8') @@ -61,7 +59,6 @@ def test_catalog_write_mo(tempdir): assert read_mo(f) is not None -@with_tempdir def test_get_catalogs_for_xx(tempdir): (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') @@ -85,7 +82,6 @@ def test_get_catalogs_for_xx(tempdir): ]) -@with_tempdir def test_get_catalogs_for_en(tempdir): (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'xx_dom.po').write_text('#') @@ -97,7 +93,6 @@ def test_get_catalogs_for_en(tempdir): assert domains == set(['en_dom']) -@with_tempdir def test_get_catalogs_with_non_existent_locale(tempdir): catalogs = i18n.find_catalog_source_files([tempdir / 'loc1'], 'xx') assert not catalogs @@ -111,7 +106,6 @@ def test_get_catalogs_with_non_existent_locale_dirs(): assert not catalogs -@with_tempdir def test_get_catalogs_for_xx_without_outdated(tempdir): (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') @@ -139,7 +133,6 @@ def test_get_catalogs_for_xx_without_outdated(tempdir): ]) -@with_tempdir def test_get_catalogs_from_multiple_locale_dirs(tempdir): (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') @@ -152,7 +145,6 @@ def test_get_catalogs_from_multiple_locale_dirs(tempdir): assert domains == ['test1', 'test1', 'test2'] -@with_tempdir def test_get_catalogs_with_compact(tempdir): (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') From f566f003f3a78c4bf822e97c47a45c0c605b9d2b Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Fri, 6 Jan 2017 00:26:31 +0900 Subject: [PATCH 05/38] pytest: nits --- tests/test_setup_command.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py index c7874eb25..67f46e1a8 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -90,7 +90,8 @@ def nonascii_srcdir(request, setup_command): ).encode('utf-8')) -def test_build_sphinx_with_nonascii_path(setup_command, nonascii_srcdir): +@pytest.mark.usefixtures('nonascii_srcdir') +def test_build_sphinx_with_nonascii_path(setup_command): proc = setup_command.proc out, err = proc.communicate() print(out) From f962ad67d230860a4fb1e04e1a9ced5629eaa524 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Fri, 6 Jan 2017 00:46:42 +0900 Subject: [PATCH 06/38] pytest: remove deprecated raises and raises_msg assert functions --- tests/test_application.py | 22 +++++++----- tests/test_config.py | 42 ++++++++++++++--------- tests/test_domain_cpp.py | 49 +++++++++++++++++---------- tests/test_ext_inheritance_diagram.py | 11 +++--- tests/test_quickstart.py | 6 ++-- tests/test_theming.py | 9 +++-- tests/test_util_i18n.py | 6 ++-- tests/test_websupport.py | 31 ++++++++++------- tests/test_writer_latex.py | 5 +-- 9 files changed, 115 insertions(+), 66 deletions(-) diff --git a/tests/test_application.py b/tests/test_application.py index 1580b8036..18db64a57 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -16,18 +16,21 @@ from sphinx.application import ExtensionError from sphinx.domains import Domain from util import with_app, raises_msg, strip_escseq +import pytest @with_app() def test_events(app, status, warning): def empty(): pass - raises_msg(ExtensionError, "Unknown event name: invalid", - app.connect, "invalid", empty) + with pytest.raises(ExtensionError) as excinfo: + app.connect("invalid", empty) + assert "Unknown event name: invalid" in str(excinfo.value) app.add_event("my_event") - raises_msg(ExtensionError, "Event 'my_event' already present", - app.add_event, "my_event") + with pytest.raises(ExtensionError) as excinfo: + app.add_event("my_event") + assert "Event 'my_event' already present" in str(excinfo.value) def mock_callback(a_app, *args): assert a_app is app @@ -109,12 +112,15 @@ def test_domain_override(app, status, warning): name = 'foo' # No domain know named foo. - raises_msg(ExtensionError, 'domain foo not yet registered', - app.override_domain, A) + with pytest.raises(ExtensionError) as excinfo: + app.override_domain(A) + assert 'domain foo not yet registered' in str(excinfo.value) + assert app.add_domain(A) is None assert app.override_domain(B) is None - raises_msg(ExtensionError, 'new domain not a subclass of registered ' - 'foo domain', app.override_domain, C) + with pytest.raises(ExtensionError) as excinfo: + app.override_domain(C) + assert 'new domain not a subclass of registered foo domain' in str(excinfo.value) @with_app(testroot='add_source_parser') diff --git a/tests/test_config.py b/tests/test_config.py index 2139c334e..26a43a9c6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,10 +10,11 @@ :license: BSD, see LICENSE for details. """ from six import PY3, iteritems +import pytest import mock from util import TestApp, with_app, gen_with_app, \ - raises, raises_msg, assert_in, assert_not_in + assert_in, assert_not_in import sphinx from sphinx.config import Config @@ -55,11 +56,14 @@ def test_core_config(app, status, warning): assert 'nonexisting_value' not in cfg # invalid values - raises(AttributeError, getattr, cfg, '_value') - raises(AttributeError, getattr, cfg, 'nonexisting_value') + with pytest.raises(AttributeError): + getattr(cfg, '_value') + with pytest.raises(AttributeError): + getattr(cfg, 'nonexisting_value') # non-value attributes are deleted from the namespace - raises(AttributeError, getattr, cfg, 'sys') + with pytest.raises(AttributeError): + getattr(cfg, 'sys') # setting attributes cfg.project = 'Foo' @@ -80,16 +84,20 @@ def test_extension_values(app, status, warning): assert cfg.value_from_conf_py == 84 # no duplicate values allowed - raises_msg(ExtensionError, 'already present', app.add_config_value, - 'html_title', 'x', True) - raises_msg(ExtensionError, 'already present', app.add_config_value, - 'value_from_ext', 'x', True) + with pytest.raises(ExtensionError) as excinfo: + app.add_config_value('html_title', 'x', True) + assert 'already present' in str(excinfo.value) + with pytest.raises(ExtensionError) as excinfo: + app.add_config_value('value_from_ext', 'x', True) + assert 'already present' in str(excinfo.value) def test_errors_warnings(tempdir): # test the error for syntax errors in the config file (tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii') - raises_msg(ConfigError, 'conf.py', Config, tempdir, 'conf.py', {}, None) + with pytest.raises(ConfigError) as excinfo: + Config(tempdir, 'conf.py', {}, None) + assert 'conf.py' in str(excinfo.value) # test the automatic conversion of 2.x only code in configs (tempdir / 'conf.py').write_text( @@ -119,7 +127,9 @@ def test_errors_warnings(tempdir): def test_errors_if_setup_is_not_callable(tempdir): # test the error to call setup() in the config file (tempdir / 'conf.py').write_text(u'setup = 1') - raises_msg(ConfigError, 'callable', TestApp, srcdir=tempdir) + with pytest.raises(ConfigError) as excinfo: + TestApp(srcdir=tempdir) + assert 'callable' in str(excinfo.value) @mock.patch.object(sphinx, '__display_version__', '1.3.4') @@ -129,24 +139,24 @@ def test_needs_sphinx(): app.cleanup() app = TestApp(confoverrides={'needs_sphinx': '1.3.4'}) # OK: equals app.cleanup() - raises(VersionRequirementError, TestApp, - confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater + with pytest.raises(VersionRequirementError): + TestApp(confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater # minor version app = TestApp(confoverrides={'needs_sphinx': '1.2'}) # OK: less app.cleanup() app = TestApp(confoverrides={'needs_sphinx': '1.3'}) # OK: equals app.cleanup() - raises(VersionRequirementError, TestApp, - confoverrides={'needs_sphinx': '1.4'}) # NG: greater + with pytest.raises(VersionRequirementError): + TestApp(confoverrides={'needs_sphinx': '1.4'}) # NG: greater # major version app = TestApp(confoverrides={'needs_sphinx': '0'}) # OK: less app.cleanup() app = TestApp(confoverrides={'needs_sphinx': '1'}) # OK: equals app.cleanup() - raises(VersionRequirementError, TestApp, - confoverrides={'needs_sphinx': '2'}) # NG: greater + with pytest.raises(VersionRequirementError): + TestApp(confoverrides={'needs_sphinx': '2'}) # NG: greater def test_config_eol(tempdir): diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 4a505119d..490f775dd 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -12,8 +12,9 @@ import re from six import text_type +import pytest -from util import raises, with_app +from util import with_app from sphinx import addnodes from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError @@ -150,9 +151,10 @@ def test_concept_definitions(): None, 'I0EN1A1B7ConceptE') check('concept', 'template Foo()', None, 'I00DpE3Foo') - raises(DefinitionError, parse, 'concept', 'Foo') - raises(DefinitionError, parse, 'concept', - 'template template Foo') + with pytest.raises(DefinitionError): + parse('concept', 'Foo') + with pytest.raises(DefinitionError): + parse('concept', 'template template Foo') def test_member_definitions(): @@ -259,9 +261,12 @@ def test_function_definitions(): 'int foo(Foo f = Foo(double(), std::make_pair(int(2), double(3.4))))', "foo__Foo", "3foo3Foo") check('function', 'int foo(A a = x(a))', "foo__A", "3foo1A") - raises(DefinitionError, parse, 'function', 'int foo(B b=x(a)') - raises(DefinitionError, parse, 'function', 'int foo)C c=x(a))') - raises(DefinitionError, parse, 'function', 'int foo(D d=x(a') + with pytest.raises(DefinitionError): + parse('function', 'int foo(B b=x(a)') + with pytest.raises(DefinitionError): + parse('function', 'int foo)C c=x(a))') + with pytest.raises(DefinitionError): + parse('function', 'int foo(D d=x(a') check('function', 'int foo(const A&... a)', "foo__ACRDp", "3fooDpRK1A") check('function', 'virtual void f()', "f", "1fv") # test for ::nestedName, from issue 1738 @@ -382,8 +387,10 @@ def test_templates(): check('function', "template<> void A()", None, "IE1Av") check('member', "template<> A a", None, "IE1a") check('type', "template<> a = A", None, "IE1a") - raises(DefinitionError, parse, 'enum', "template<> A") - raises(DefinitionError, parse, 'enumerator', "template<> A") + with pytest.raises(DefinitionError): + parse('enum', "template<> A") + with pytest.raises(DefinitionError): + parse('enumerator', "template<> A") # then all the real tests check('class', "template A", None, "I00E1A") check('type', "template<> a", None, "IE1a") @@ -419,8 +426,10 @@ def test_templates(): "RK18c_string_view_baseIK4Char6TraitsE") # template introductions - raises(DefinitionError, parse, 'enum', 'abc::ns::foo{id_0, id_1, id_2} A') - raises(DefinitionError, parse, 'enumerator', 'abc::ns::foo{id_0, id_1, id_2} A') + with pytest.raises(DefinitionError): + parse('enum', 'abc::ns::foo{id_0, id_1, id_2} A') + with pytest.raises(DefinitionError): + parse('enumerator', 'abc::ns::foo{id_0, id_1, id_2} A') check('class', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar', None, 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE') check('class', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar', @@ -469,12 +478,18 @@ def test_attributes(): check('member', 'paren_attr(a) int f', 'f__i', '1f') check('member', 'paren_attr("") int f', 'f__i', '1f') check('member', 'paren_attr(()[{}][]{}) int f', 'f__i', '1f') - raises(DefinitionError, parse, 'member', 'paren_attr(() int f') - raises(DefinitionError, parse, 'member', 'paren_attr([) int f') - raises(DefinitionError, parse, 'member', 'paren_attr({) int f') - raises(DefinitionError, parse, 'member', 'paren_attr([)]) int f') - raises(DefinitionError, parse, 'member', 'paren_attr((])) int f') - raises(DefinitionError, parse, 'member', 'paren_attr({]}) int f') + with pytest.raises(DefinitionError): + parse('member', 'paren_attr(() int f') + with pytest.raises(DefinitionError): + parse('member', 'paren_attr([) int f') + with pytest.raises(DefinitionError): + parse('member', 'paren_attr({) int f') + with pytest.raises(DefinitionError): + parse('member', 'paren_attr([)]) int f') + with pytest.raises(DefinitionError): + parse('member', 'paren_attr((])) int f') + with pytest.raises(DefinitionError): + parse('member', 'paren_attr({]}) int f') # position: decl specs check('function', 'static inline __attribute__(()) void f()', diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index fb1d127d9..a7f24f3c0 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -11,7 +11,7 @@ import re import sys -from util import with_app, rootdir, raises +from util import with_app, rootdir from sphinx.ext.inheritance_diagram import InheritanceException, import_classes import pytest @@ -53,8 +53,10 @@ def test_import_classes(): from example.sphinx import DummyClass # got exception for unknown class or module - raises(InheritanceException, import_classes, 'unknown', None) - raises(InheritanceException, import_classes, 'unknown.Unknown', None) + with pytest.raises(InheritanceException): + import_classes('unknown', None) + with pytest.raises(InheritanceException): + import_classes('unknown.Unknown', None) # a module having no classes classes = import_classes('sphinx', None) @@ -80,7 +82,8 @@ def test_import_classes(): assert classes == [CatalogInfo] # got exception for functions - raises(InheritanceException, import_classes, 'encode_uri', 'sphinx.util') + with pytest.raises(InheritanceException): + import_classes('encode_uri', 'sphinx.util') # import submodule on current module (refs: #3164) classes = import_classes('sphinx', 'example') diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 80d9c60b6..eb1d32409 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -14,8 +14,9 @@ import time from six import PY2, text_type, StringIO from six.moves import input +import pytest -from util import raises, SkipTest +from util import SkipTest from sphinx import application from sphinx import quickstart as qs @@ -107,7 +108,8 @@ def test_do_prompt(): assert d['k4'] is True qs.do_prompt(d, 'k5', 'Q5', validator=qs.boolean) assert d['k5'] is False - raises(AssertionError, qs.do_prompt, d, 'k6', 'Q6', validator=qs.boolean) + with pytest.raises(AssertionError): + qs.do_prompt(d, 'k6', 'Q6', validator=qs.boolean) def test_do_prompt_with_nonascii(): diff --git a/tests/test_theming.py b/tests/test_theming.py index bb27f9677..a2ac046cd 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -13,10 +13,11 @@ import os import zipfile import mock +import pytest from sphinx.theming import Theme, ThemeError -from util import with_app, raises, path +from util import with_app, path @with_app(confoverrides={'html_theme': 'ziptheme', @@ -46,10 +47,12 @@ def test_theme_api(app, status, warning): assert theme.get_confstr('options', 'nosidebar') == 'false' # nonexisting setting assert theme.get_confstr('theme', 'foobar', 'def') == 'def' - raises(ThemeError, theme.get_confstr, 'theme', 'foobar') + with pytest.raises(ThemeError): + theme.get_confstr('theme', 'foobar') # options API - raises(ThemeError, theme.get_options, {'nonexisting': 'foo'}) + with pytest.raises(ThemeError): + theme.get_options({'nonexisting': 'foo'}) options = theme.get_options(cfg.html_theme_options) assert options['testopt'] == 'foo' assert options['nosidebar'] == 'false' diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 38bfddea4..3fbdd8830 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -17,8 +17,9 @@ from os import path from babel.messages.mofile import read_mo from sphinx.util import i18n from sphinx.errors import SphinxError +import pytest -from util import TestApp, raises +from util import TestApp def test_catalog_info_for_file_and_path(): @@ -261,4 +262,5 @@ def test_get_filename_for_language(): # invalid figure_language_filename app.env.config.figure_language_filename = '{root}.{invalid}{ext}' - raises(SphinxError, i18n.get_image_filename_for_language, 'foo.png', app.env) + with pytest.raises(SphinxError): + i18n.get_image_filename_for_language('foo.png', app.env) diff --git a/tests/test_websupport.py b/tests/test_websupport.py index af9ed91ee..3592dd5ec 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, raises, skip_if +from util import rootdir, tempdir, skip_if @pytest.fixture @@ -53,7 +53,8 @@ class NullStorage(StorageBackend): @with_support(storage=NullStorage()) def test_no_srcdir(support): # make sure the correct exception is raised if srcdir is not given. - raises(RuntimeError, support.build) + with pytest.raises(RuntimeError): + support.build() @skip_if(sqlalchemy_missing, 'needs sqlalchemy') @@ -65,7 +66,8 @@ def test_build(support): @skip_if(sqlalchemy_missing, 'needs sqlalchemy') @with_support() def test_get_document(support): - raises(DocumentNotFoundError, support.get_document, 'nonexisting') + with pytest.raises(DocumentNotFoundError): + support.get_document('nonexisting') contents = support.get_document('contents') assert contents['title'] and contents['body'] \ @@ -90,8 +92,8 @@ def test_comments(support): # Make sure that comments can't be added to a comment where # displayed == False, since it could break the algorithm that # converts a nodes comments to a tree. - raises(CommentNotAllowedError, support.add_comment, 'Not allowed', - parent_id=str(hidden_comment['id'])) + with pytest.raises(CommentNotAllowedError): + support.add_comment('Not allowed', parent_id=str(hidden_comment['id'])) # Add a displayed and not displayed child to the displayed comment. support.add_comment('Child test comment', parent_id=str(comment['id']), username='user_one') @@ -133,8 +135,8 @@ def test_user_delete_comments(support): comment = get_comment() assert comment['username'] == 'user_one' # Make sure other normal users can't delete someone elses comments. - raises(UserNotAuthorizedError, support.delete_comment, - comment['id'], username='user_two') + with pytest.raises(UserNotAuthorizedError): + support.delete_comment(comment['id'], username='user_two') # Now delete the comment using the correct username. support.delete_comment(comment['id'], username='user_one') comment = get_comment() @@ -164,8 +166,10 @@ def test_moderation(support): # Make sure the moderation_callback is called. assert called # Make sure the user must be a moderator. - raises(UserNotAuthorizedError, support.accept_comment, accepted['id']) - raises(UserNotAuthorizedError, support.delete_comment, deleted['id']) + with pytest.raises(UserNotAuthorizedError): + support.accept_comment(accepted['id']) + with pytest.raises(UserNotAuthorizedError): + support.delete_comment(deleted['id']) support.accept_comment(accepted['id'], moderator=True) support.delete_comment(deleted['id'], moderator=True) comments = support.get_data(node.id)['comments'] @@ -186,7 +190,8 @@ def test_moderator_delete_comments(support): comment = get_comment() support.delete_comment(comment['id'], username='user_two', moderator=True) - raises(IndexError, get_comment) + with pytest.raises(IndexError): + get_comment() @skip_if(sqlalchemy_missing, 'needs sqlalchemy') @@ -248,8 +253,10 @@ def test_voting(support): check_rating(2) # Make sure a vote with value > 1 or < -1 can't be cast. - raises(ValueError, support.process_vote, comment['id'], 'user_one', '2') - raises(ValueError, support.process_vote, comment['id'], 'user_one', '-2') + with pytest.raises(ValueError): + support.process_vote(comment['id'], 'user_one', '2') + with pytest.raises(ValueError): + support.process_vote(comment['id'], 'user_one', '-2') # Make sure past voting data is associated with comments when they are # fetched. diff --git a/tests/test_writer_latex.py b/tests/test_writer_latex.py index 72eb7ed2a..33803299b 100644 --- a/tests/test_writer_latex.py +++ b/tests/test_writer_latex.py @@ -11,7 +11,7 @@ from __future__ import print_function from sphinx.writers.latex import rstdim_to_latexdim -from util import raises +import pytest def test_rstdim_to_latexdim(): @@ -32,5 +32,6 @@ def test_rstdim_to_latexdim(): assert rstdim_to_latexdim('.5em') == '.5em' # unknown values (it might be generated by 3rd party extension) - raises(ValueError, rstdim_to_latexdim, 'unknown') + with pytest.raises(ValueError): + rstdim_to_latexdim('unknown') assert rstdim_to_latexdim('160.0unknown') == '160.0unknown' From b3c207dd4961b3ee3dd8c045734cb06b456e8dd8 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Fri, 6 Jan 2017 00:50:02 +0900 Subject: [PATCH 07/38] fix --- tests/test_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_application.py b/tests/test_application.py index 18db64a57..e5c00fc8d 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -15,7 +15,7 @@ from docutils import nodes from sphinx.application import ExtensionError from sphinx.domains import Domain -from util import with_app, raises_msg, strip_escseq +from util import with_app, strip_escseq import pytest From baaef9146d46e772a680ecfe3d16e51806890156 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Fri, 6 Jan 2017 01:14:47 +0900 Subject: [PATCH 08/38] pytest: remove deprecated with_app decorator functions --- tests/test_api_translator.py | 37 +++---- tests/test_application.py | 13 +-- tests/test_build.py | 11 +- tests/test_build_applehelp.py | 9 +- tests/test_build_gettext.py | 16 +-- tests/test_build_html.py | 9 +- tests/test_build_latex.py | 145 +++++++++++++++----------- tests/test_build_linkcheck.py | 9 +- tests/test_build_manpage.py | 4 +- tests/test_build_texinfo.py | 7 +- tests/test_catalogs.py | 17 +-- tests/test_config.py | 18 ++-- tests/test_directive_code.py | 36 ++++--- tests/test_directive_only.py | 5 +- tests/test_docutilsconf.py | 19 ++-- tests/test_domain_cpp.py | 7 +- tests/test_environment_toctree.py | 5 +- tests/test_ext_autodoc.py | 5 +- tests/test_ext_autosectionlabel.py | 4 +- tests/test_ext_autosummary.py | 4 +- tests/test_ext_coverage.py | 4 +- tests/test_ext_doctest.py | 5 +- tests/test_ext_githubpages.py | 4 +- tests/test_ext_graphviz.py | 8 +- tests/test_ext_ifconfig.py | 4 +- tests/test_ext_inheritance_diagram.py | 6 +- tests/test_ext_intersphinx.py | 5 - tests/test_ext_math.py | 18 ++-- tests/test_ext_todo.py | 7 +- tests/test_ext_viewcode.py | 6 +- tests/test_highlighting.py | 3 - tests/test_intl.py | 7 +- tests/test_markup.py | 11 +- tests/test_metadata.py | 4 +- tests/test_search.py | 17 ++- tests/test_templating.py | 6 +- tests/test_theming.py | 9 +- tests/test_toctree.py | 6 +- 38 files changed, 264 insertions(+), 246 deletions(-) diff --git a/tests/test_api_translator.py b/tests/test_api_translator.py index 7a70fd4c8..5f2707cc3 100644 --- a/tests/test_api_translator.py +++ b/tests/test_api_translator.py @@ -11,7 +11,8 @@ import sys -from util import with_app, rootdir +import pytest +from util import rootdir def setup_module(): @@ -22,7 +23,7 @@ def teardown_module(): sys.path.remove(rootdir / 'roots' / 'test-api-set-translator') -@with_app('html') +@pytest.mark.sphinx('html') def test_html_translator(app, status, warning): # no set_translator(), no html_translator_class translator_class = app.builder.translator_class @@ -30,7 +31,7 @@ def test_html_translator(app, status, warning): assert translator_class.__name__ == 'SmartyPantsHTMLTranslator' -@with_app('html', confoverrides={ +@pytest.mark.sphinx('html', confoverrides={ 'html_translator_class': 'translator.ExtHTMLTranslator'}) def test_html_with_html_translator_class(app, status, warning): # no set_translator(), but html_translator_class @@ -39,8 +40,8 @@ def test_html_with_html_translator_class(app, status, warning): assert translator_class.__name__ == 'ExtHTMLTranslator' -@with_app('html', - confoverrides={'html_use_smartypants': False}) +@pytest.mark.sphinx('html', confoverrides={ + 'html_use_smartypants': False}) def test_html_with_smartypants(app, status, warning): # no set_translator(), html_use_smartypants=False translator_class = app.builder.translator_class @@ -48,7 +49,7 @@ def test_html_with_smartypants(app, status, warning): assert translator_class.__name__ == 'HTMLTranslator' -@with_app('html', testroot='api-set-translator') +@pytest.mark.sphinx('html', testroot='api-set-translator') def test_html_with_set_translator_for_html_(app, status, warning): # use set_translator(), no html_translator_class translator_class = app.builder.translator_class @@ -56,8 +57,8 @@ def test_html_with_set_translator_for_html_(app, status, warning): assert translator_class.__name__ == 'ConfHTMLTranslator' -@with_app('html', testroot='api-set-translator', - confoverrides={'html_translator_class': 'translator.ExtHTMLTranslator'}) +@pytest.mark.sphinx('html', testroot='api-set-translator', confoverrides={ + 'html_translator_class': 'translator.ExtHTMLTranslator'}) def test_html_with_set_translator_for_html_and_html_translator_class( app, status, warning): # use set_translator() and html_translator_class. @@ -68,7 +69,7 @@ def test_html_with_set_translator_for_html_and_html_translator_class( # this test break test_websupport.test_comments test. why? -# @with_app( +# @pytest.mark.sphinx( # buildername='dirhtml', # srcdir=(test_roots / 'test-api-set-translator'), # ) @@ -78,63 +79,63 @@ def test_html_with_set_translator_for_html_and_html_translator_class( # assert translator_class.__name__ == 'ConfDirHTMLTranslator' -@with_app('singlehtml', testroot='api-set-translator') +@pytest.mark.sphinx('singlehtml', testroot='api-set-translator') def test_singlehtml_set_translator_for_singlehtml(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfSingleHTMLTranslator' -@with_app('pickle', testroot='api-set-translator') +@pytest.mark.sphinx('pickle', testroot='api-set-translator') def test_pickle_set_translator_for_pickle(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfPickleTranslator' -@with_app('json', testroot='api-set-translator') +@pytest.mark.sphinx('json', testroot='api-set-translator') def test_json_set_translator_for_json(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfJsonTranslator' -@with_app('latex', testroot='api-set-translator') +@pytest.mark.sphinx('latex', testroot='api-set-translator') def test_html_with_set_translator_for_latex(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfLaTeXTranslator' -@with_app('man', testroot='api-set-translator') +@pytest.mark.sphinx('man', testroot='api-set-translator') def test_html_with_set_translator_for_man(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfManualPageTranslator' -@with_app('texinfo', testroot='api-set-translator') +@pytest.mark.sphinx('texinfo', testroot='api-set-translator') def test_html_with_set_translator_for_texinfo(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfTexinfoTranslator' -@with_app('text', testroot='api-set-translator') +@pytest.mark.sphinx('text', testroot='api-set-translator') def test_html_with_set_translator_for_text(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfTextTranslator' -@with_app('xml', testroot='api-set-translator') +@pytest.mark.sphinx('xml', testroot='api-set-translator') def test_html_with_set_translator_for_xml(app, status, warning): translator_class = app.builder.translator_class assert translator_class assert translator_class.__name__ == 'ConfXMLTranslator' -@with_app('pseudoxml', testroot='api-set-translator') +@pytest.mark.sphinx('pseudoxml', testroot='api-set-translator') def test_html_with_set_translator_for_pseudoxml(app, status, warning): translator_class = app.builder.translator_class assert translator_class diff --git a/tests/test_application.py b/tests/test_application.py index e5c00fc8d..00223350b 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -15,11 +15,10 @@ from docutils import nodes from sphinx.application import ExtensionError from sphinx.domains import Domain -from util import with_app, strip_escseq +from util import strip_escseq import pytest -@with_app() def test_events(app, status, warning): def empty(): pass @@ -45,13 +44,11 @@ def test_events(app, status, warning): "Callback called when disconnected" -@with_app() def test_emit_with_nonascii_name_node(app, status, warning): node = nodes.section(names=[u'\u65e5\u672c\u8a9e']) app.emit('my_event', node) -@with_app() def test_output(app, status, warning): # info with newline status.truncate(0) # __init__ writes to status @@ -71,7 +68,6 @@ def test_output(app, status, warning): assert app._warncount == old_count + 1 -@with_app() def test_output_with_unencodable_char(app, status, warning): class StreamWriter(codecs.StreamWriter): @@ -87,20 +83,17 @@ def test_output_with_unencodable_char(app, status, warning): assert status.getvalue() == "unicode ?...\n" -@with_app() def test_extensions(app, status, warning): app.setup_extension('shutil') assert strip_escseq(warning.getvalue()).startswith("WARNING: extension 'shutil'") -@with_app() def test_extension_in_blacklist(app, status, warning): app.setup_extension('sphinxjp.themecore') msg = strip_escseq(warning.getvalue()) assert msg.startswith("WARNING: the extension 'sphinxjp.themecore' was") -@with_app() def test_domain_override(app, status, warning): class A(Domain): name = 'foo' @@ -123,7 +116,7 @@ def test_domain_override(app, status, warning): assert 'new domain not a subclass of registered foo domain' in str(excinfo.value) -@with_app(testroot='add_source_parser') +@pytest.mark.sphinx(testroot='add_source_parser') def test_add_source_parser(app, status, warning): assert set(app.config.source_suffix) == set(['.rst', '.md', '.test']) assert set(app.config.source_parsers.keys()) == set(['.md', '.test']) @@ -131,7 +124,7 @@ def test_add_source_parser(app, status, warning): assert app.config.source_parsers['.test'].__name__ == 'TestSourceParser' -@with_app(testroot='add_source_parser-conflicts-with-users-setting') +@pytest.mark.sphinx(testroot='add_source_parser-conflicts-with-users-setting') def test_add_source_parser_conflicts_with_users_setting(app, status, warning): assert set(app.config.source_suffix) == set(['.rst', '.test']) assert set(app.config.source_parsers.keys()) == set(['.test']) diff --git a/tests/test_build.py b/tests/test_build.py index c99d87312..d623d9b88 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -9,16 +9,15 @@ :license: BSD, see LICENSE for details. """ -from six import BytesIO - import pickle from docutils import nodes import mock +import pytest from textwrap import dedent from sphinx.errors import SphinxError import sphinx.builders.linkcheck -from util import with_app, rootdir, tempdir, SkipTest, TestApp, path +from util import rootdir, tempdir, SkipTest, TestApp, path try: from docutils.writers.manpage import Writer as ManWriter @@ -91,7 +90,7 @@ def test_master_doc_not_found(tempdir): app.cleanup() -@with_app(buildername='text', testroot='circular') +@pytest.mark.sphinx(buildername='text', testroot='circular') def test_circular_toctree(app, status, warning): app.builder.build_all() warnings = warning.getvalue() @@ -103,7 +102,7 @@ def test_circular_toctree(app, status, warning): 'contents <- sub <- contents') in warnings -@with_app(buildername='text', testroot='numbered-circular') +@pytest.mark.sphinx(buildername='text', testroot='numbered-circular') def test_numbered_circular_toctree(app, status, warning): app.builder.build_all() warnings = warning.getvalue() @@ -115,7 +114,7 @@ def test_numbered_circular_toctree(app, status, warning): 'contents <- sub <- contents') in warnings -@with_app(buildername='dummy', testroot='image-glob') +@pytest.mark.sphinx(buildername='dummy', testroot='image-glob') def test_image_glob(app, status, warning): app.builder.build_all() diff --git a/tests/test_build_applehelp.py b/tests/test_build_applehelp.py index 63bb0ec89..48324e259 100644 --- a/tests/test_build_applehelp.py +++ b/tests/test_build_applehelp.py @@ -13,7 +13,7 @@ import plistlib -from util import with_app +import pytest from path import path # Use plistlib.load in 3.4 and above @@ -43,9 +43,10 @@ def check_localization(outdir): assert (lprojdir / 'localized.txt').isfile() -@with_app(buildername='applehelp', testroot='basic', srcdir='applehelp_output', - confoverrides={'applehelp_bundle_id': 'org.sphinx-doc.Sphinx.help', - 'applehelp_disable_external_tools': True}) +@pytest.mark.sphinx( + 'applehelp', testroot='basic', srcdir='applehelp_output', + confoverrides={'applehelp_bundle_id': 'org.sphinx-doc.Sphinx.help', + 'applehelp_disable_external_tools': True}) def test_applehelp_output(app, status, warning): (app.srcdir / 'en.lproj').makedirs() (app.srcdir / 'en.lproj' / 'localized.txt').write_text('') diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py index 0ba6d5534..cbc17ab80 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -15,8 +15,10 @@ import re import gettext from subprocess import Popen, PIPE +import pytest + from util import ( - with_app, gen_with_app, SkipTest, assert_in, assert_true, assert_equal + gen_with_app, SkipTest, assert_in, assert_true, assert_equal ) @@ -76,8 +78,9 @@ def test_all(app, status, warning): yield assert_equal, _("Testing various markup"), u"Testing various markup" -@with_app('gettext', testroot='intl', srcdir='gettext', - confoverrides={'gettext_compact': False}) +@pytest.mark.sphinx( + 'gettext', testroot='intl', srcdir='gettext', + confoverrides={'gettext_compact': False}) def test_gettext_index_entries(app, status, warning): # regression test for #976 app.builder.build(['index_entries']) @@ -123,8 +126,9 @@ def test_gettext_index_entries(app, status, warning): assert msgids == [] -@with_app('gettext', testroot='intl', srcdir='gettext', - confoverrides={'gettext_compact': False, 'gettext_additional_targets': []}) +@pytest.mark.sphinx( + 'gettext', testroot='intl', srcdir='gettext', + confoverrides={'gettext_compact': False, 'gettext_additional_targets': []}) def test_gettext_disable_index_entries(app, status, warning): # regression test for #976 app.builder.build(['index_entries']) @@ -155,7 +159,7 @@ def test_gettext_disable_index_entries(app, status, warning): assert msgids == [] -@with_app(buildername='gettext', testroot='intl', srcdir='gettext') +@pytest.mark.sphinx('gettext', testroot='intl', srcdir='gettext') def test_gettext_template(app, status, warning): 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 e45c70052..3b8da34f0 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -15,9 +15,10 @@ import re from six import PY3, iteritems from sphinx import __display_version__ -from util import remove_unicode_literals, gen_with_app, with_app, strip_escseq +from util import remove_unicode_literals, gen_with_app, strip_escseq from etree13 import ElementTree from html5lib import getTreeBuilder, HTMLParser +import pytest TREE_BUILDER = getTreeBuilder('etree', implementation=ElementTree) @@ -384,7 +385,7 @@ def check_extra_entries(outdir): assert (outdir / 'robots.txt').isfile() -@with_app(buildername='html', testroot='warnings', freshenv=True) +@pytest.mark.sphinx('html', testroot='warnings', freshenv=True) def test_html_warnings(app, status, warning): app.builder.build_all() html_warnings = strip_escseq(warning.getvalue().replace(os.sep, '/')) @@ -1084,7 +1085,7 @@ def test_enumerable_node(app, status, warning): yield check_xpath, etree, fname, xpath, check, be_found -@with_app(buildername='html', testroot='html_assets') +@pytest.mark.sphinx('html', testroot='html_assets') def test_html_assets(app, status, warning): app.builder.build_all() @@ -1112,7 +1113,7 @@ def test_html_assets(app, status, warning): assert not (app.outdir / 'subdir' / '.htpasswd').exists() -@with_app(buildername='html', confoverrides={'html_sourcelink_suffix': ''}) +@pytest.mark.sphinx('html', confoverrides={'html_sourcelink_suffix': ''}) def test_html_sourcelink_suffix(app, status, warning): app.builder.build_all() content_otherext = (app.outdir / 'otherext.html').text() diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 7366763ca..027df463a 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -22,7 +22,7 @@ from sphinx.errors import SphinxError from sphinx.util.osutil import cd, ensuredir from sphinx.writers.latex import LaTeXTranslator -from util import SkipTest, remove_unicode_literals, with_app, strip_escseq, skip_if +from util import SkipTest, remove_unicode_literals, strip_escseq, skip_if from test_build_html import ENV_WARNINGS @@ -109,7 +109,7 @@ def test_build_latex_doc(app, status, warning, engine, docclass): compile_latex_document(app) -@with_app(buildername='latex') +@pytest.mark.sphinx('latex') def test_writer(app, status, warning): app.builder.build_all() result = (app.outdir / 'SphinxTests.tex').text(encoding='utf8') @@ -137,7 +137,7 @@ def test_writer(app, status, warning): '\\end{wrapfigure}' in result) -@with_app(buildername='latex', testroot='warnings', freshenv=True) +@pytest.mark.sphinx('latex', testroot='warnings', freshenv=True) def test_latex_warnings(app, status, warning): app.builder.build_all() @@ -150,7 +150,7 @@ def test_latex_warnings(app, status, warning): '--- Got:\n' + warnings -@with_app(buildername='latex', testroot='basic') +@pytest.mark.sphinx('latex', testroot='basic') def test_latex_title(app, status, warning): app.builder.build_all() result = (app.outdir / 'test.tex').text(encoding='utf8') @@ -160,7 +160,7 @@ def test_latex_title(app, status, warning): assert '\\title{The basic Sphinx documentation for testing}' in result -@with_app(buildername='latex', testroot='latex-title') +@pytest.mark.sphinx('latex', testroot='latex-title') def test_latex_title_after_admonitions(app, status, warning): app.builder.build_all() result = (app.outdir / 'test.tex').text(encoding='utf8') @@ -170,7 +170,7 @@ def test_latex_title_after_admonitions(app, status, warning): assert '\\title{test-latex-title}' in result -@with_app(buildername='latex', testroot='numfig', +@pytest.mark.sphinx('latex', testroot='numfig', confoverrides={'numfig': True}) def test_numref(app, status, warning): app.builder.build_all() @@ -203,12 +203,13 @@ def test_numref(app, status, warning): '\\nameref{\\detokenize{foo:foo}}}') in result -@with_app(buildername='latex', testroot='numfig', - confoverrides={'numfig': True, - 'numfig_format': {'figure': 'Figure:%s', - 'table': 'Tab_%s', - 'code-block': 'Code-%s', - 'section': 'SECTION-%s'}}) +@pytest.mark.sphinx( + 'latex', testroot='numfig', + confoverrides={'numfig': True, + 'numfig_format': {'figure': 'Figure:%s', + 'table': 'Tab_%s', + 'code-block': 'Code-%s', + 'section': 'SECTION-%s'}}) def test_numref_with_prefix1(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -246,12 +247,13 @@ def test_numref_with_prefix1(app, status, warning): '\\nameref{\\detokenize{foo:foo}}}') in result -@with_app(buildername='latex', testroot='numfig', - confoverrides={'numfig': True, - 'numfig_format': {'figure': 'Figure:%s.', - 'table': 'Tab_%s:', - 'code-block': 'Code-%s | ', - 'section': 'SECTION_%s_'}}) +@pytest.mark.sphinx( + 'latex', testroot='numfig', + confoverrides={'numfig': True, + 'numfig_format': {'figure': 'Figure:%s.', + 'table': 'Tab_%s:', + 'code-block': 'Code-%s | ', + 'section': 'SECTION_%s_'}}) def test_numref_with_prefix2(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -285,8 +287,9 @@ def test_numref_with_prefix2(app, status, warning): '\\nameref{\\detokenize{foo:foo}}}') in result -@with_app(buildername='latex', testroot='numfig', - confoverrides={'numfig': True, 'language': 'ja'}) +@pytest.mark.sphinx( + 'latex', testroot='numfig', + confoverrides={'numfig': True, 'language': 'ja'}) def test_numref_with_language_ja(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -318,7 +321,7 @@ def test_numref_with_language_ja(app, status, warning): '\\nameref{\\detokenize{foo:foo}}}') in result -@with_app(buildername='latex') +@pytest.mark.sphinx('latex') def test_latex_add_latex_package(app, status, warning): app.add_latex_package('foo') app.add_latex_package('bar', 'baz') @@ -328,7 +331,7 @@ def test_latex_add_latex_package(app, status, warning): assert '\\usepackage[baz]{bar}' in result -@with_app(buildername='latex', testroot='latex-babel') +@pytest.mark.sphinx('latex', testroot='latex-babel') def test_babel_with_no_language_settings(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -347,8 +350,9 @@ def test_babel_with_no_language_settings(app, status, warning): assert '\\shorthandoff' not in result -@with_app(buildername='latex', testroot='latex-babel', - confoverrides={'language': 'de'}) +@pytest.mark.sphinx( + 'latex', testroot='latex-babel', + confoverrides={'language': 'de'}) def test_babel_with_language_de(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -367,8 +371,9 @@ def test_babel_with_language_de(app, status, warning): assert '\\shorthandoff{"}' in result -@with_app(buildername='latex', testroot='latex-babel', - confoverrides={'language': 'ru'}) +@pytest.mark.sphinx( + 'latex', testroot='latex-babel', + confoverrides={'language': 'ru'}) def test_babel_with_language_ru(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -388,8 +393,9 @@ def test_babel_with_language_ru(app, status, warning): assert '\\shorthandoff' not in result -@with_app(buildername='latex', testroot='latex-babel', - confoverrides={'language': 'tr'}) +@pytest.mark.sphinx( + 'latex', testroot='latex-babel', + confoverrides={'language': 'tr'}) def test_babel_with_language_tr(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -408,8 +414,9 @@ def test_babel_with_language_tr(app, status, warning): assert '\\shorthandoff{=}' in result -@with_app(buildername='latex', testroot='latex-babel', - confoverrides={'language': 'ja'}) +@pytest.mark.sphinx( + 'latex', testroot='latex-babel', + confoverrides={'language': 'ja'}) def test_babel_with_language_ja(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -427,8 +434,9 @@ def test_babel_with_language_ja(app, status, warning): assert '\\shorthandoff' not in result -@with_app(buildername='latex', testroot='latex-babel', - confoverrides={'language': 'unknown'}) +@pytest.mark.sphinx( + 'latex', testroot='latex-babel', + confoverrides={'language': 'unknown'}) def test_babel_with_unknown_language(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -449,7 +457,7 @@ def test_babel_with_unknown_language(app, status, warning): assert "WARNING: no Babel option known for language 'unknown'" in warning.getvalue() -@with_app(buildername='latex') +@pytest.mark.sphinx('latex') def test_footnote(app, status, warning): app.builder.build_all() result = (app.outdir / 'SphinxTests.tex').text(encoding='utf8') @@ -479,7 +487,7 @@ def test_footnote(app, status, warning): 'footnotes in table\n%\n\\end{footnotetext}') in result -@with_app(buildername='latex', testroot='footnotes') +@pytest.mark.sphinx('latex', testroot='footnotes') def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -516,8 +524,9 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning): assert '\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]' in result -@with_app(buildername='latex', testroot='footnotes', - confoverrides={'latex_show_urls': 'inline'}) +@pytest.mark.sphinx( + 'latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'inline'}) def test_latex_show_urls_is_inline(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -559,8 +568,9 @@ def test_latex_show_urls_is_inline(app, status, warning): '{sphinx-dev@googlegroups.com}') in result -@with_app(buildername='latex', testroot='footnotes', - confoverrides={'latex_show_urls': 'footnote'}) +@pytest.mark.sphinx( + 'latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'footnote'}) def test_latex_show_urls_is_footnote(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -610,8 +620,9 @@ def test_latex_show_urls_is_footnote(app, status, warning): '{sphinx-dev@googlegroups.com}\n') in result -@with_app(buildername='latex', testroot='footnotes', - confoverrides={'latex_show_urls': 'no'}) +@pytest.mark.sphinx( + 'latex', testroot='footnotes', + confoverrides={'latex_show_urls': 'no'}) def test_latex_show_urls_is_no(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -651,7 +662,7 @@ def test_latex_show_urls_is_no(app, status, warning): '{sphinx-dev@googlegroups.com}\n') in result -@with_app(buildername='latex', testroot='image-in-section') +@pytest.mark.sphinx('latex', testroot='image-in-section') def test_image_in_section(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -667,7 +678,7 @@ def test_image_in_section(app, status, warning): assert ('\\chapter{Another section}' in result) -@with_app(buildername='latex', confoverrides={'latex_logo': 'notfound.jpg'}) +@pytest.mark.sphinx('latex', confoverrides={'latex_logo': 'notfound.jpg'}) def test_latex_logo_if_not_found(app, status, warning): try: app.builder.build_all() @@ -676,7 +687,7 @@ def test_latex_logo_if_not_found(app, status, warning): assert isinstance(exc, SphinxError) -@with_app(buildername='latex', testroot='toctree-maxdepth', +@pytest.mark.sphinx('latex', testroot='toctree-maxdepth', confoverrides={'latex_documents': [ ('index', 'SphinxTests.tex', 'Sphinx Tests Documentation', 'Georg Brandl', 'manual'), @@ -691,11 +702,12 @@ def test_toctree_maxdepth_manual(app, status, warning): assert '\\setcounter{secnumdepth}' not in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'latex_documents': [ - ('index', 'SphinxTests.tex', 'Sphinx Tests Documentation', - 'Georg Brandl', 'howto'), - ]}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'latex_documents': [ + ('index', 'SphinxTests.tex', 'Sphinx Tests Documentation', + 'Georg Brandl', 'howto'), + ]}) def test_toctree_maxdepth_howto(app, status, warning): app.builder.build_all() result = (app.outdir / 'SphinxTests.tex').text(encoding='utf8') @@ -706,8 +718,9 @@ def test_toctree_maxdepth_howto(app, status, warning): assert '\\setcounter{secnumdepth}' not in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'foo'}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'master_doc': 'foo'}) def test_toctree_not_found(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -718,8 +731,9 @@ def test_toctree_not_found(app, status, warning): assert '\\setcounter{secnumdepth}' not in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'bar'}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'master_doc': 'bar'}) def test_toctree_without_maxdepth(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -730,8 +744,9 @@ def test_toctree_without_maxdepth(app, status, warning): assert '\\setcounter{secnumdepth}' not in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'master_doc': 'qux'}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'master_doc': 'qux'}) def test_toctree_with_deeper_maxdepth(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -742,8 +757,9 @@ def test_toctree_with_deeper_maxdepth(app, status, warning): assert '\\setcounter{secnumdepth}{3}' in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'latex_toplevel_sectioning': None}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'latex_toplevel_sectioning': None}) def test_latex_toplevel_sectioning_is_None(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -753,8 +769,9 @@ def test_latex_toplevel_sectioning_is_None(app, status, warning): assert '\\chapter{Foo}' in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'latex_toplevel_sectioning': 'part'}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'latex_toplevel_sectioning': 'part'}) def test_latex_toplevel_sectioning_is_part(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -764,8 +781,9 @@ def test_latex_toplevel_sectioning_is_part(app, status, warning): assert '\\part{Foo}' in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'latex_toplevel_sectioning': 'chapter'}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'latex_toplevel_sectioning': 'chapter'}) def test_latex_toplevel_sectioning_is_chapter(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -775,8 +793,9 @@ def test_latex_toplevel_sectioning_is_chapter(app, status, warning): assert '\\chapter{Foo}' in result -@with_app(buildername='latex', testroot='toctree-maxdepth', - confoverrides={'latex_toplevel_sectioning': 'section'}) +@pytest.mark.sphinx( + 'latex', testroot='toctree-maxdepth', + confoverrides={'latex_toplevel_sectioning': 'section'}) def test_latex_toplevel_sectioning_is_section(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -786,7 +805,7 @@ def test_latex_toplevel_sectioning_is_section(app, status, warning): assert '\\section{Foo}' in result @skip_if_stylefiles_notfound -@with_app(buildername='latex', testroot='maxlistdepth') +@pytest.mark.sphinx('latex', testroot='maxlistdepth') def test_maxlistdepth_at_ten(app, status, warning): app.builder.build_all() result = (app.outdir / 'SphinxTests.tex').text(encoding='utf8') diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index 1d75135af..f776f772a 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -10,10 +10,10 @@ """ from __future__ import print_function -from util import with_app +import pytest -@with_app('linkcheck', testroot='linkcheck', freshenv=True) +@pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True) def test_defaults(app, status, warning): app.builder.build_all() @@ -26,8 +26,9 @@ def test_defaults(app, status, warning): assert len(content.splitlines()) == 1 -@with_app('linkcheck', testroot='linkcheck', freshenv=True, - confoverrides={'linkcheck_anchors_ignore': ["^!", "^top$"]}) +@pytest.mark.sphinx( + 'linkcheck', testroot='linkcheck', freshenv=True, + confoverrides={'linkcheck_anchors_ignore': ["^!", "^top$"]}) def test_anchors_ignored(app, status, warning): app.builder.build_all() diff --git a/tests/test_build_manpage.py b/tests/test_build_manpage.py index 8da2eaadd..b91a4fd29 100644 --- a/tests/test_build_manpage.py +++ b/tests/test_build_manpage.py @@ -10,10 +10,10 @@ """ from __future__ import print_function -from util import with_app +import pytest -@with_app(buildername='man') +@pytest.mark.sphinx('man') def test_all(app, status, warning): app.builder.build_all() assert (app.outdir / 'SphinxTests.1').exists() diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py index 06da311df..bd7bd6270 100644 --- a/tests/test_build_texinfo.py +++ b/tests/test_build_texinfo.py @@ -15,10 +15,11 @@ import re from subprocess import Popen, PIPE from six import PY3 +import pytest from sphinx.writers.texinfo import TexinfoTranslator -from util import SkipTest, remove_unicode_literals, with_app, strip_escseq +from util import SkipTest, remove_unicode_literals, strip_escseq from test_build_html import ENV_WARNINGS @@ -33,7 +34,7 @@ if PY3: TEXINFO_WARNINGS = remove_unicode_literals(TEXINFO_WARNINGS) -@with_app(buildername='texinfo', testroot='warnings', freshenv=True) +@pytest.mark.sphinx('texinfo', testroot='warnings', freshenv=True) def test_texinfo_warnings(app, status, warning): app.builder.build_all() warnings = strip_escseq(warning.getvalue().replace(os.sep, '/')) @@ -45,7 +46,7 @@ def test_texinfo_warnings(app, status, warning): '--- Got:\n' + warnings -@with_app(buildername='texinfo') +@pytest.mark.sphinx('texinfo') def test_texinfo(app, status, warning): TexinfoTranslator.ignore_missing_images = True app.builder.build_all() diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index d0606d945..7e395274c 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -12,7 +12,7 @@ import shutil import pytest -from util import with_app, find_files, rootdir, tempdir +from util import find_files, rootdir, tempdir root = tempdir / 'test-intl' build_dir = root / '_build' @@ -37,8 +37,9 @@ def setup_test(): @pytest.mark.usefixtures('setup_test') -@with_app(buildername='html', testroot='intl', - confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) +@pytest.mark.sphinx( + 'html', testroot='intl', + confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) def test_compile_all_catalogs(app, status, warning): app.builder.compile_all_catalogs() @@ -53,8 +54,9 @@ def test_compile_all_catalogs(app, status, warning): @pytest.mark.usefixtures('setup_test') -@with_app(buildername='html', testroot='intl', - confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) +@pytest.mark.sphinx( + 'html', testroot='intl', + confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) def test_compile_specific_catalogs(app, status, warning): catalog_dir = locale_dir / app.config.language / 'LC_MESSAGES' @@ -68,8 +70,9 @@ def test_compile_specific_catalogs(app, status, warning): @pytest.mark.usefixtures('setup_test') -@with_app(buildername='html', testroot='intl', - confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) +@pytest.mark.sphinx( + 'html', testroot='intl', + confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) def test_compile_update_catalogs(app, status, warning): app.builder.compile_update_catalogs() diff --git a/tests/test_config.py b/tests/test_config.py index 26a43a9c6..aef5ed846 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,7 +13,7 @@ from six import PY3, iteritems import pytest import mock -from util import TestApp, with_app, gen_with_app, \ +from util import TestApp, gen_with_app, \ assert_in, assert_not_in import sphinx @@ -21,9 +21,11 @@ from sphinx.config import Config from sphinx.errors import ExtensionError, ConfigError, VersionRequirementError -@with_app(confoverrides={'master_doc': 'master', 'nonexisting_value': 'True', - 'latex_elements.docclass': 'scrartcl', - 'modindex_common_prefix': 'path1,path2'}) +@pytest.mark.sphinx(confoverrides={ + 'master_doc': 'master', + 'nonexisting_value': 'True', + 'latex_elements.docclass': 'scrartcl', + 'modindex_common_prefix': 'path1,path2'}) def test_core_config(app, status, warning): cfg = app.config @@ -74,7 +76,7 @@ def test_core_config(app, status, warning): assert cfg['project'] == cfg.project == 'Sphinx Tests' -@with_app() +@pytest.mark.sphinx() def test_extension_values(app, status, warning): cfg = app.config @@ -169,7 +171,7 @@ def test_config_eol(tempdir): assert cfg.project == u'spam' -@with_app(confoverrides={'master_doc': 123, +@pytest.mark.sphinx(confoverrides={'master_doc': 123, 'language': 'foo', 'primary_domain': None}) def test_builtin_conf(app, status, warning): @@ -215,13 +217,13 @@ def test_gen_check_types(app, status, warning): ) -@with_app(testroot='config') +@pytest.mark.sphinx(testroot='config') def test_check_enum(app, status, warning): assert "The config value `value17` has to be a one of ('default', 'one', 'two'), " \ not in warning.getvalue() -@with_app(testroot='config', confoverrides={'value17': 'invalid'}) +@pytest.mark.sphinx(testroot='config', confoverrides={'value17': 'invalid'}) def test_check_enum_failed(app, status, warning): assert "The config value `value17` has to be a one of ('default', 'one', 'two'), " \ "but `invalid` is given." in warning.getvalue() diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index cb6221355..f168eafbe 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -9,10 +9,12 @@ :license: BSD, see LICENSE for details. """ -from util import with_app, etree_parse +import pytest + +from util import etree_parse -@with_app('xml', testroot='directive-code') +@pytest.mark.sphinx('xml', testroot='directive-code') def test_code_block(app, status, warning): app.builder.build('index') et = etree_parse(app.outdir / 'index.xml') @@ -28,7 +30,7 @@ def test_code_block(app, status, warning): assert actual == expect -@with_app('xml', testroot='directive-code') +@pytest.mark.sphinx('xml', testroot='directive-code') def test_code_block_dedent(app, status, warning): app.builder.build(['dedent_code']) et = etree_parse(app.outdir / 'dedent_code.xml') @@ -47,7 +49,7 @@ def test_code_block_dedent(app, status, warning): assert blocks[5].text == '\n\n' # dedent: 1000 -@with_app('html', testroot='directive-code') +@pytest.mark.sphinx('html', testroot='directive-code') def test_code_block_caption_html(app, status, warning): app.builder.build(['caption']) html = (app.outdir / 'caption.html').text(encoding='utf-8') @@ -59,7 +61,7 @@ def test_code_block_caption_html(app, status, warning): assert caption in html -@with_app('latex', testroot='directive-code') +@pytest.mark.sphinx('latex', testroot='directive-code') def test_code_block_caption_latex(app, status, warning): app.builder.build_all() latex = (app.outdir / 'Python.tex').text(encoding='utf-8') @@ -72,7 +74,7 @@ def test_code_block_caption_latex(app, status, warning): assert link in latex -@with_app('latex', testroot='directive-code') +@pytest.mark.sphinx('latex', testroot='directive-code') def test_code_block_namedlink_latex(app, status, warning): app.builder.build_all() latex = (app.outdir / 'Python.tex').text(encoding='utf-8') @@ -89,7 +91,7 @@ def test_code_block_namedlink_latex(app, status, warning): assert link2 in latex -@with_app('xml', testroot='directive-code') +@pytest.mark.sphinx('xml', testroot='directive-code') def test_literal_include(app, status, warning): app.builder.build(['index']) et = etree_parse(app.outdir / 'index.xml') @@ -101,7 +103,7 @@ def test_literal_include(app, status, warning): assert actual == literal_src -@with_app('xml', testroot='directive-code') +@pytest.mark.sphinx('xml', testroot='directive-code') def test_literal_include_dedent(app, status, warning): literal_src = (app.srcdir / 'literal.inc').text(encoding='utf-8') literal_lines = [l[4:] for l in literal_src.split('\n')[9:11]] @@ -119,7 +121,7 @@ def test_literal_include_dedent(app, status, warning): assert blocks[5].text == '\n\n' # dedent: 1000 -@with_app('xml', testroot='directive-code') +@pytest.mark.sphinx('xml', testroot='directive-code') def test_literal_include_block_start_with_comment_or_brank(app, status, warning): app.builder.build(['python']) et = etree_parse(app.outdir / 'python.xml') @@ -143,7 +145,7 @@ def test_literal_include_block_start_with_comment_or_brank(app, status, warning) assert actual == expect -@with_app('html', testroot='directive-code') +@pytest.mark.sphinx('html', testroot='directive-code') def test_literal_include_linenos(app, status, warning): app.builder.build(['linenos']) html = (app.outdir / 'linenos.html').text(encoding='utf-8') @@ -166,7 +168,7 @@ def test_literal_include_linenos(app, status, warning): assert linenos in html -@with_app('html', testroot='directive-code') +@pytest.mark.sphinx('html', testroot='directive-code') def test_literal_include_lineno_start(app, status, warning): app.builder.build(['lineno_start']) html = (app.outdir / 'lineno_start.html').text(encoding='utf-8') @@ -189,7 +191,7 @@ def test_literal_include_lineno_start(app, status, warning): assert linenos in html -@with_app('html', testroot='directive-code') +@pytest.mark.sphinx('html', testroot='directive-code') def test_literal_include_lineno_match(app, status, warning): app.builder.build(['lineno_match']) html = (app.outdir / 'lineno_match.html').text(encoding='utf-8') @@ -229,7 +231,7 @@ def test_literal_include_lineno_match(app, status, warning): assert start_at_end_at in html -@with_app('latex', testroot='directive-code') +@pytest.mark.sphinx('latex', testroot='directive-code') def test_literalinclude_file_whole_of_emptyline(app, status, warning): app.builder.build_all() latex = (app.outdir / 'Python.tex').text(encoding='utf-8').replace('\r\n', '\n') @@ -243,7 +245,7 @@ def test_literalinclude_file_whole_of_emptyline(app, status, warning): assert includes in latex -@with_app('html', testroot='directive-code') +@pytest.mark.sphinx('html', testroot='directive-code') def test_literalinclude_caption_html(app, status, warning): app.builder.build('index') html = (app.outdir / 'caption.html').text(encoding='utf-8') @@ -255,7 +257,7 @@ def test_literalinclude_caption_html(app, status, warning): assert caption in html -@with_app('latex', testroot='directive-code') +@pytest.mark.sphinx('latex', testroot='directive-code') def test_literalinclude_caption_latex(app, status, warning): app.builder.build('index') latex = (app.outdir / 'Python.tex').text(encoding='utf-8') @@ -268,7 +270,7 @@ def test_literalinclude_caption_latex(app, status, warning): assert link in latex -@with_app('latex', testroot='directive-code') +@pytest.mark.sphinx('latex', testroot='directive-code') def test_literalinclude_namedlink_latex(app, status, warning): app.builder.build('index') latex = (app.outdir / 'Python.tex').text(encoding='utf-8') @@ -285,7 +287,7 @@ def test_literalinclude_namedlink_latex(app, status, warning): assert link2 in latex -@with_app('xml', testroot='directive-code') +@pytest.mark.sphinx('xml', testroot='directive-code') def test_literalinclude_classes(app, status, warning): app.builder.build(['classes']) et = etree_parse(app.outdir / 'classes.xml') diff --git a/tests/test_directive_only.py b/tests/test_directive_only.py index def064c5a..30b569a12 100644 --- a/tests/test_directive_only.py +++ b/tests/test_directive_only.py @@ -13,11 +13,10 @@ import re from docutils import nodes from sphinx.util.nodes import process_only_nodes - -from util import with_app +import pytest -@with_app('text', testroot='directive-only') +@pytest.mark.sphinx('text', testroot='directive-only') def test_sectioning(app, status, warning): def getsects(section): diff --git a/tests/test_docutilsconf.py b/tests/test_docutilsconf.py index ebd0782bd..89ec63107 100644 --- a/tests/test_docutilsconf.py +++ b/tests/test_docutilsconf.py @@ -11,14 +11,15 @@ import re -from util import with_app, path, SkipTest +import pytest +from util import path, SkipTest def regex_count(expr, result): return len(re.findall(expr, result)) -@with_app('html', testroot='docutilsconf', freshenv=True, docutilsconf='') +@pytest.mark.sphinx('html', testroot='docutilsconf', freshenv=True, docutilsconf='') def test_html_with_default_docutilsconf(app, status, warning): app.builder.build(['contents']) result = (app.outdir / 'contents.html').text(encoding='utf-8') @@ -29,7 +30,7 @@ def test_html_with_default_docutilsconf(app, status, warning): assert regex_count(r'', result) == 1 -@with_app('html', testroot='docutilsconf', freshenv=True, docutilsconf=( +@pytest.mark.sphinx('html', testroot='docutilsconf', freshenv=True, docutilsconf=( '\n[html4css1 writer]' '\noption-limit:1' '\nfield-name-limit:1' @@ -45,31 +46,31 @@ def test_html_with_docutilsconf(app, status, warning): assert regex_count(r'', result) == 2 -@with_app('html', testroot='docutilsconf') +@pytest.mark.sphinx('html', testroot='docutilsconf') def test_html(app, status, warning): app.builder.build(['contents']) assert warning.getvalue() == '' -@with_app('latex', testroot='docutilsconf') +@pytest.mark.sphinx('latex', testroot='docutilsconf') def test_latex(app, status, warning): app.builder.build(['contents']) assert warning.getvalue() == '' -@with_app('man', testroot='docutilsconf') +@pytest.mark.sphinx('man', testroot='docutilsconf') def test_man(app, status, warning): app.builder.build(['contents']) assert warning.getvalue() == '' -@with_app('texinfo', testroot='docutilsconf') +@pytest.mark.sphinx('texinfo', testroot='docutilsconf') def test_texinfo(app, status, warning): app.builder.build(['contents']) -@with_app('html', testroot='docutilsconf', - docutilsconf='[general]\nsource_link=true\n') +@pytest.mark.sphinx('html', testroot='docutilsconf', + docutilsconf='[general]\nsource_link=true\n') def test_docutils_source_link_with_nonascii_file(app, status, warning): srcdir = path(app.srcdir) mb_name = u'\u65e5\u672c\u8a9e' diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 490f775dd..58e6093ba 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -14,8 +14,6 @@ import re from six import text_type import pytest -from util import with_app - from sphinx import addnodes from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError from sphinx.domains.cpp import Symbol @@ -505,7 +503,7 @@ def test_attributes(): # raise DefinitionError("") -@with_app(testroot='domain-cpp', confoverrides={'add_function_parentheses': True}) +@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': True}) def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, warning): app.builder.build_all() @@ -542,7 +540,8 @@ def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, war check(s, t, f) -@with_app(testroot='domain-cpp', confoverrides={'add_function_parentheses': False}) +@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={ + 'add_function_parentheses': False}) def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning): app.builder.build_all() diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py index 20188c16a..037109126 100644 --- a/tests/test_environment_toctree.py +++ b/tests/test_environment_toctree.py @@ -14,8 +14,9 @@ from docutils.nodes import bullet_list, list_item, caption, comment, reference from sphinx import addnodes from sphinx.addnodes import compact_paragraph, only from sphinx.builders.html import StandaloneHTMLBuilder +import pytest -from util import with_app, gen_with_app, assert_node +from util import gen_with_app, assert_node @gen_with_app('xml', testroot='toctree') @@ -97,7 +98,7 @@ def _test_process_doc(app): assert 'qux' not in app.env.toctree_includes -@with_app('dummy', testroot='toctree-glob') +@pytest.mark.sphinx('dummy', testroot='toctree-glob') def test_glob(app, status, warning): includefiles = ['foo', 'bar/index', 'bar/bar_1', 'bar/bar_2', 'bar/bar_3', 'baz', 'qux/index'] diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index dad7521af..873fc23c9 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -10,12 +10,11 @@ """ import pickle -from docutils import nodes +import pytest from sphinx import addnodes -from util import with_app -@with_app(buildername='dummy', testroot='ext-autodoc') +@pytest.mark.sphinx('dummy', testroot='ext-autodoc') def test_autodoc(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_autosectionlabel.py b/tests/test_ext_autosectionlabel.py index ff7cd6776..71d19e800 100644 --- a/tests/test_ext_autosectionlabel.py +++ b/tests/test_ext_autosectionlabel.py @@ -11,10 +11,10 @@ import re -from util import with_app +import pytest -@with_app('html', testroot='ext-autosectionlabel') +@pytest.mark.sphinx('html', testroot='ext-autosectionlabel') def test_autosectionlabel_html(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index f8a8a3900..84c73d632 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -13,7 +13,7 @@ from six import iteritems, StringIO from sphinx.ext.autosummary import mangle_signature -from util import with_app +import pytest html_warnfile = StringIO() @@ -54,7 +54,7 @@ def test_mangle_signature(): assert res == outp, (u"'%s' -> '%s' != '%s'" % (inp, res, outp)) -@with_app(buildername='dummy', **default_kw) +@pytest.mark.sphinx('dummy', **default_kw) def test_get_items_summary(app, status, warning): # monkey-patch Autosummary.get_items so we can easily get access to it's # results.. diff --git a/tests/test_ext_coverage.py b/tests/test_ext_coverage.py index ddf8efe51..4ae18b66e 100644 --- a/tests/test_ext_coverage.py +++ b/tests/test_ext_coverage.py @@ -11,10 +11,10 @@ import pickle -from util import with_app +import pytest -@with_app(buildername='coverage') +@pytest.mark.sphinx('coverage') def test_build(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_doctest.py b/tests/test_ext_doctest.py index d2a2d90ce..6b17f2ed7 100644 --- a/tests/test_ext_doctest.py +++ b/tests/test_ext_doctest.py @@ -8,13 +8,12 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ - -from util import with_app +import pytest cleanup_called = 0 -@with_app(buildername='doctest', testroot='doctest') +@pytest.mark.sphinx('doctest', testroot='doctest') def test_build(app, status, warning): global cleanup_called cleanup_called = 0 diff --git a/tests/test_ext_githubpages.py b/tests/test_ext_githubpages.py index f74ed5315..2eff6be78 100644 --- a/tests/test_ext_githubpages.py +++ b/tests/test_ext_githubpages.py @@ -9,10 +9,10 @@ :license: BSD, see LICENSE for details. """ -from util import with_app +import pytest -@with_app('html', testroot='ext-githubpages') +@pytest.mark.sphinx('html', testroot='ext-githubpages') def test_githubpages(app, status, warning): app.builder.build_all() assert (app.outdir / '.nojekyll').exists() diff --git a/tests/test_ext_graphviz.py b/tests/test_ext_graphviz.py index e59143d37..45850f3ce 100644 --- a/tests/test_ext_graphviz.py +++ b/tests/test_ext_graphviz.py @@ -13,10 +13,8 @@ import re import pytest -from util import with_app, SkipTest - -@with_app('html', testroot='ext-graphviz') +@pytest.mark.sphinx('html', testroot='ext-graphviz') @pytest.mark.usefixtures('if_graphviz_found') def test_graphviz_html(app, status, warning): app.builder.build_all() @@ -37,7 +35,7 @@ def test_graphviz_html(app, status, warning): assert re.search(html, content, re.S) -@with_app('latex', testroot='ext-graphviz') +@pytest.mark.sphinx('latex', testroot='ext-graphviz') @pytest.mark.usefixtures('if_graphviz_found') def test_graphviz_latex(app, status, warning): app.builder.build_all() @@ -57,7 +55,7 @@ def test_graphviz_latex(app, status, warning): assert re.search(macro, content, re.S) -@with_app('html', testroot='ext-graphviz', confoverrides={'language': 'xx'}) +@pytest.mark.sphinx('html', testroot='ext-graphviz', confoverrides={'language': 'xx'}) @pytest.mark.usefixtures('if_graphviz_found') def test_graphviz_i18n(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_ifconfig.py b/tests/test_ext_ifconfig.py index 835ab0938..1ed7c78d2 100644 --- a/tests/test_ext_ifconfig.py +++ b/tests/test_ext_ifconfig.py @@ -9,10 +9,10 @@ :license: BSD, see LICENSE for details. """ -from util import with_app +import pytest -@with_app(buildername='text', testroot='ext-ifconfig') +@pytest.mark.sphinx('text', testroot='ext-ifconfig') def test_ifconfig(app, status, warning): app.builder.build_all() result = (app.outdir / 'index.txt').text() diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index a7f24f3c0..3ce4b23a5 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -11,12 +11,12 @@ import re import sys -from util import with_app, rootdir +from util import rootdir from sphinx.ext.inheritance_diagram import InheritanceException, import_classes import pytest -@with_app('html', testroot='ext-inheritance_diagram') +@pytest.mark.sphinx('html', testroot='ext-inheritance_diagram') @pytest.mark.usefixtures('if_graphviz_found') def test_inheritance_diagram_html(app, status, warning): app.builder.build_all() @@ -31,7 +31,7 @@ def test_inheritance_diagram_html(app, status, warning): assert re.search(pattern, content, re.M) -@with_app('latex', testroot='ext-inheritance_diagram') +@pytest.mark.sphinx('latex', testroot='ext-inheritance_diagram') @pytest.mark.usefixtures('if_graphviz_found') def test_inheritance_diagram_latex(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index 92a9b8d33..a913102b1 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -23,8 +23,6 @@ from sphinx.ext.intersphinx import read_inventory, \ load_mappings, missing_reference, _strip_basic_auth, _read_from_url, \ _get_safe_url, fetch_inventory, INVENTORY_FILENAME -from util import with_app - inventory_v1 = '''\ # Sphinx inventory version 1 @@ -82,7 +80,6 @@ def test_read_inventory_v2(): '/util/glossary.html#term-a-term-including-colon' -@with_app() @mock.patch('sphinx.ext.intersphinx.read_inventory') @mock.patch('sphinx.ext.intersphinx._read_from_url') def test_fetch_inventory_redirection(_read_from_url, read_inventory, app, status, warning): @@ -127,7 +124,6 @@ def test_fetch_inventory_redirection(_read_from_url, read_inventory, app, status assert read_inventory.call_args[0][1] == 'http://hostname/' -@with_app() def test_missing_reference(tempdir, app, status, warning): inv_file = tempdir / 'inventory' inv_file.write_bytes(inventory_v2) @@ -217,7 +213,6 @@ def test_missing_reference(tempdir, app, status, warning): assert rn['refuri'] == '../../../../py3k/foo.html#module-module1' -@with_app() def test_load_mappings_warnings(tempdir, app, status, warning): """ load_mappings issues a warning if new-style mapping diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 0c7d44e8e..0e02c924f 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -11,11 +11,13 @@ import re -from util import with_app, SkipTest +import pytest +from util import SkipTest -@with_app(buildername='html', testroot='ext-math', - confoverrides = {'extensions': ['sphinx.ext.jsmath'], 'jsmath_path': 'dummy.js'}) +@pytest.mark.sphinx( + 'html', testroot='ext-math', + confoverrides = {'extensions': ['sphinx.ext.jsmath'], 'jsmath_path': 'dummy.js'}) def test_jsmath(app, status, warning): app.builder.build_all() content = (app.outdir / 'math.html').text() @@ -33,7 +35,7 @@ def test_jsmath(app, status, warning): assert '
\na + 1 < b
' in content -@with_app('html', testroot='ext-math-simple', +@pytest.mark.sphinx('html', testroot='ext-math-simple', confoverrides = {'extensions': ['sphinx.ext.imgmath']}) def test_imgmath_png(app, status, warning): app.builder.build_all() @@ -48,7 +50,7 @@ def test_imgmath_png(app, status, warning): assert re.search(html, content, re.S) -@with_app('html', testroot='ext-math-simple', +@pytest.mark.sphinx('html', testroot='ext-math-simple', confoverrides={'extensions': ['sphinx.ext.imgmath'], 'imgmath_image_format': 'svg'}) def test_imgmath_svg(app, status, warning): @@ -64,7 +66,7 @@ def test_imgmath_svg(app, status, warning): assert re.search(html, content, re.S) -@with_app('html', testroot='ext-math', +@pytest.mark.sphinx('html', testroot='ext-math', confoverrides={'extensions': ['sphinx.ext.mathjax']}) def test_mathjax_align(app, status, warning): app.builder.build_all() @@ -76,7 +78,7 @@ def test_mathjax_align(app, status, warning): assert re.search(html, content, re.S) -@with_app('html', testroot='ext-math', +@pytest.mark.sphinx('html', testroot='ext-math', confoverrides={'math_number_all': True, 'extensions': ['sphinx.ext.mathjax']}) def test_math_number_all_mathjax(app, status, warning): @@ -88,7 +90,7 @@ def test_math_number_all_mathjax(app, status, warning): assert re.search(html, content, re.S) -@with_app('latex', testroot='ext-math', +@pytest.mark.sphinx('latex', testroot='ext-math', confoverrides={'extensions': ['sphinx.ext.mathjax']}) def test_math_number_all_latex(app, status, warning): app.builder.build_all() diff --git a/tests/test_ext_todo.py b/tests/test_ext_todo.py index 269a8a2be..cbea9b74c 100644 --- a/tests/test_ext_todo.py +++ b/tests/test_ext_todo.py @@ -10,10 +10,11 @@ """ import re -from util import with_app + +import pytest -@with_app('html', testroot='ext-todo', freshenv=True, +@pytest.mark.sphinx('html', testroot='ext-todo', freshenv=True, confoverrides={'todo_include_todos': True, 'todo_emit_warnings': True}) def test_todo(app, status, warning): todos = [] @@ -49,7 +50,7 @@ def test_todo(app, status, warning): assert set(todo[1].astext() for todo in todos) == set(['todo in foo', 'todo in bar']) -@with_app('html', testroot='ext-todo', freshenv=True, +@pytest.mark.sphinx('html', testroot='ext-todo', freshenv=True, confoverrides={'todo_include_todos': False, 'todo_emit_warnings': True}) def test_todo_not_included(app, status, warning): todos = [] diff --git a/tests/test_ext_viewcode.py b/tests/test_ext_viewcode.py index 93e681a5d..e4763119a 100644 --- a/tests/test_ext_viewcode.py +++ b/tests/test_ext_viewcode.py @@ -11,10 +11,10 @@ import re -from util import with_app +import pytest -@with_app(testroot='ext-viewcode') +@pytest.mark.sphinx(testroot='ext-viewcode') def test_viewcode(app, status, warning): app.builder.build_all() @@ -32,7 +32,7 @@ def test_viewcode(app, status, warning): assert result.count('href="_modules/spam/mod2.html#Class2"') == 2 -@with_app(testroot='ext-viewcode', tags=['test_linkcode']) +@pytest.mark.sphinx(testroot='ext-viewcode', tags=['test_linkcode']) def test_linkcode(app, status, warning): app.builder.build(['objects']) diff --git a/tests/test_highlighting.py b/tests/test_highlighting.py index 328abdf31..44a1cb3c1 100644 --- a/tests/test_highlighting.py +++ b/tests/test_highlighting.py @@ -16,8 +16,6 @@ from pygments.formatters.html import HtmlFormatter from sphinx.highlighting import PygmentsBridge -from util import with_app - class MyLexer(RegexLexer): name = 'testlexer' @@ -41,7 +39,6 @@ class ComplainOnUnhighlighted(PygmentsBridge): raise AssertionError("should highlight %r" % source) -@with_app() def test_add_lexer(app, status, warning): app.add_lexer('test', MyLexer()) diff --git a/tests/test_intl.py b/tests/test_intl.py index 9fe5a21eb..cb9d2cecf 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -19,8 +19,9 @@ from subprocess import Popen, PIPE from babel.messages import pofile from six import string_types +import pytest -from util import tempdir, rootdir, path, gen_with_app, with_app, SkipTest, \ +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 @@ -845,7 +846,7 @@ def test_references(app, status, warning): yield assert_count(warning_expr, warnings, 0) -@with_app(buildername='dummy', testroot='image-glob', confoverrides={'language': 'xx'}) +@pytest.mark.sphinx('dummy', testroot='image-glob', confoverrides={'language': 'xx'}) def test_image_glob_intl(app, status, warning): app.builder.build_all() @@ -886,7 +887,7 @@ def test_image_glob_intl(app, status, warning): 'image/svg+xml': 'subdir/svgimg.xx.svg'}) -@with_app(buildername='dummy', testroot='image-glob', +@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): diff --git a/tests/test_markup.py b/tests/test_markup.py index ec203447f..9bfb44e25 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -20,8 +20,9 @@ from sphinx.util import texescape from sphinx.util.docutils import sphinx_domains from sphinx.writers.html import HTMLWriter, SmartyPantsHTMLTranslator from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator +import pytest -from util import TestApp, with_app, assert_node +from util import TestApp, assert_node app = settings = parser = domain_context = None @@ -152,7 +153,7 @@ def test_latex_escaping(): r'\\href{http://example.com/~me/}{test}.*') -@with_app(buildername='dummy', testroot='prolog') +@pytest.mark.sphinx('dummy', testroot='prolog') def test_rst_prolog(app, status, warning): app.builder.build_all() rst = pickle.loads((app.doctreedir / 'restructuredtext.doctree').bytes()) @@ -176,7 +177,7 @@ def test_rst_prolog(app, status, warning): assert not md.rawsource.endswith('*Good-bye world*.\n') -@with_app(buildername='dummy', testroot='keep_warnings') +@pytest.mark.sphinx('dummy', testroot='keep_warnings') def test_keep_warnings_is_True(app, status, warning): app.builder.build_all() doctree = pickle.loads((app.doctreedir / 'index.doctree').bytes()) @@ -185,7 +186,7 @@ def test_keep_warnings_is_True(app, status, warning): assert_node(doctree[0][1], nodes.system_message) -@with_app(buildername='dummy', testroot='keep_warnings', +@pytest.mark.sphinx('dummy', testroot='keep_warnings', confoverrides={'keep_warnings': False}) def test_keep_warnings_is_False(app, status, warning): app.builder.build_all() @@ -194,7 +195,7 @@ def test_keep_warnings_is_False(app, status, warning): assert len(doctree[0]) == 1 -@with_app(buildername='dummy', testroot='refonly_bullet_list') +@pytest.mark.sphinx('dummy', testroot='refonly_bullet_list') def test_compact_refonly_bullet_list(app, status, warning): app.builder.build_all() doctree = pickle.loads((app.doctreedir / 'index.doctree').bytes()) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 2bb1d746c..868b05139 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -12,10 +12,10 @@ # adapted from an example of bibliographic metadata at # http://docutils.sourceforge.net/docs/user/rst/demo.txt -from util import with_app +import pytest -@with_app('pseudoxml') +@pytest.mark.sphinx('pseudoxml') def test_docinfo(app, status, warning): """ Inspect the 'docinfo' metadata stored in the first node of the document. diff --git a/tests/test_search.py b/tests/test_search.py index fb7d47d6f..143a3db2b 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -15,8 +15,7 @@ from docutils.parsers import rst from sphinx.search import IndexBuilder from sphinx.util import jsdump - -from util import with_app +import pytest settings = parser = None @@ -58,7 +57,7 @@ def test_wordcollector(): assert 'fermion' in ix._mapping -@with_app(testroot='ext-viewcode') +@pytest.mark.sphinx(testroot='ext-viewcode') def test_objects_are_escaped(app, status, warning): app.builder.build_all() searchindex = (app.outdir / 'searchindex.js').text() @@ -68,7 +67,7 @@ def test_objects_are_escaped(app, status, warning): assert 'n::Array<T, d>' in index.get('objects').get('') # n::Array is escaped -@with_app(testroot='search') +@pytest.mark.sphinx(testroot='search') def test_meta_keys_are_handled_for_language_en(app, status, warning): app.builder.build_all() searchindex = jsload(app.outdir / 'searchindex.js') @@ -81,7 +80,7 @@ def test_meta_keys_are_handled_for_language_en(app, status, warning): assert not is_registered_term(searchindex, 'onlytoogerman') -@with_app(testroot='search', confoverrides={'html_search_language': 'de'}) +@pytest.mark.sphinx(testroot='search', confoverrides={'html_search_language': 'de'}) def test_meta_keys_are_handled_for_language_de(app, status, warning): app.builder.build_all() searchindex = jsload(app.outdir / 'searchindex.js') @@ -94,14 +93,14 @@ def test_meta_keys_are_handled_for_language_de(app, status, warning): assert is_registered_term(searchindex, 'onlytoogerman') -@with_app(testroot='search') +@pytest.mark.sphinx(testroot='search') def test_stemmer_does_not_remove_short_words(app, status, warning): app.builder.build_all() searchindex = (app.outdir / 'searchindex.js').text() assert 'zfs' in searchindex -@with_app(testroot='search') +@pytest.mark.sphinx(testroot='search') def test_stemmer(app, status, warning): searchindex = jsload(app.outdir / 'searchindex.js') print(searchindex) @@ -109,7 +108,7 @@ def test_stemmer(app, status, warning): assert is_registered_term(searchindex, 'intern') -@with_app(testroot='search') +@pytest.mark.sphinx(testroot='search') def test_term_in_heading_and_section(app, status, warning): searchindex = (app.outdir / 'searchindex.js').text() # if search term is in the title of one doc and in the text of another @@ -119,7 +118,7 @@ def test_term_in_heading_and_section(app, status, warning): assert 'textinhead:0' in searchindex -@with_app(testroot='search') +@pytest.mark.sphinx(testroot='search') def test_term_in_raw_directive(app, status, warning): searchindex = jsload(app.outdir / 'searchindex.js') assert not is_registered_term(searchindex, 'raw') diff --git a/tests/test_templating.py b/tests/test_templating.py index 3cfb69ba2..aaca9d6bf 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -9,10 +9,10 @@ :license: BSD, see LICENSE for details. """ -from util import with_app +import pytest -@with_app('html', testroot='templating') +@pytest.mark.sphinx('html', testroot='templating') def test_layout_overloading(app, status, warning): app.builder.build_update() @@ -21,7 +21,7 @@ def test_layout_overloading(app, status, warning): assert '' in result -@with_app('html', testroot='templating') +@pytest.mark.sphinx('html', testroot='templating') def test_autosummary_class_template_overloading(app, status, warning): app.builder.build_update() diff --git a/tests/test_theming.py b/tests/test_theming.py index a2ac046cd..ce207a519 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -20,8 +20,9 @@ from sphinx.theming import Theme, ThemeError from util import with_app, path -@with_app(confoverrides={'html_theme': 'ziptheme', - 'html_theme_options.testopt': 'foo'}) +@pytest.mark.sphinx( + confoverrides={'html_theme': 'ziptheme', + 'html_theme_options.testopt': 'foo'}) def test_theme_api(app, status, warning): cfg = app.config @@ -62,7 +63,7 @@ def test_theme_api(app, status, warning): assert not os.path.exists(themedir) -@with_app(testroot='tocdepth') # a minimal root +@pytest.mark.sphinx(testroot='tocdepth') # a minimal root def test_js_source(app, status, warning): # Now sphinx provides non-minified JS files for jquery.js and underscore.js # to clarify the source of the minified files. see also #1434. @@ -86,7 +87,7 @@ def test_js_source(app, status, warning): assert 'Underscore.js {v}'.format(v=v) in underscore_src, msg -@with_app(testroot='double-inheriting-theme') +@pytest.mark.sphinx(testroot='double-inheriting-theme') def test_double_inheriting_theme(make_app, app_params): from sphinx.theming import load_theme_plugins # load original before patching diff --git a/tests/test_toctree.py b/tests/test_toctree.py index 64cb8cfd6..441479882 100644 --- a/tests/test_toctree.py +++ b/tests/test_toctree.py @@ -8,11 +8,9 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import pytest -from util import with_app - - -@with_app(testroot='toctree-glob') +@pytest.mark.sphinx(testroot='toctree-glob') def test_relations(app, status, warning): app.builder.build_all() assert app.builder.relations['index'] == [None, None, 'foo'] From 89e1df0bba58fc8d420fd9f293c4e325e06fc431 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Fri, 6 Jan 2017 23:10:52 +0900 Subject: [PATCH 09/38] pytest: nits --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1c5be83f6..c83c3f94b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,7 +36,7 @@ def app(app_params, make_app): """ args, kwargs = app_params app_ = make_app(*args, **kwargs) - yield app_ + return app_ @pytest.fixture(scope='function') From 70cdc7be8fdb4b802d15dc77131c4c7bced2e966 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Jan 2017 14:16:42 +0900 Subject: [PATCH 10/38] Dependency requirement updates: requests 2.4.0 or above (refs: #3268, #3310) --- CHANGES | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3baa9c17c..2ae07a1ab 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,11 @@ Release 1.5.2 (in development) =============================== +Incompatible changes +-------------------- + +* Dependency requirement updates: requests 2.4.0 or above (refs: #3268, #3310) + Features added -------------- diff --git a/setup.py b/setup.py index 3582124ac..fa0be674f 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ requires = [ 'babel>=1.3,!=2.0', 'alabaster>=0.7,<0.8', 'imagesize', - 'requests', + 'requests>=2.4.0', ] extras_require = { # Environment Marker works for wheel 0.24 or later From 62329c82f63661df334728e39d0c96ddcbd49ebd Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Jan 2017 14:29:19 +0900 Subject: [PATCH 11/38] Fix #3268: Sphinx crashes with requests package from Debian jessie --- CHANGES | 1 + sphinx/util/requests.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 2ae07a1ab..3d2bc693e 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Bugs fixed * The warning type ``misc.highlighting_failure`` does not work * #3294: ``add_latex_package()`` make crashes non-LaTeX builders * The caption of table are rendered as invalid HTML (refs: #3287) +* #3268: Sphinx crashes with requests package from Debian jessie Release 1.5.1 (released Dec 13, 2016) diff --git a/sphinx/util/requests.py b/sphinx/util/requests.py index 697e29087..8b7204c2f 100644 --- a/sphinx/util/requests.py +++ b/sphinx/util/requests.py @@ -17,7 +17,12 @@ import pkg_resources from six import string_types from six.moves.urllib.parse import urlsplit -from requests.packages.urllib3.exceptions import SSLError, InsecureRequestWarning +try: + from requests.packages.urllib3.exceptions import SSLError, InsecureRequestWarning +except ImportError: + # python-requests package in Debian jessie does not provide ``requests.packages.urllib3``. + # So try to import the exceptions from urllib3 package. + from urllib3.exceptions import SSLError, InsecureRequestWarning # try to load requests[security] try: From 5cff4b15c2f9c232b50a0b302cf25a91fb23ac0b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Jan 2017 15:36:07 +0900 Subject: [PATCH 12/38] Fix tox.ini for pytest --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 09241c1d8..b3f084a4d 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist=flake8,py27,py34,py35,py36,pypy,du13,du12,du11 [testenv] deps= six - nose + pytest sqlalchemy whoosh html5lib @@ -15,7 +15,7 @@ setenv = SPHINX_TEST_TEMPDIR = {envdir}/testbuild PYTHONDONTWRITEBYTECODE = true commands= - {envpython} -Wall tests/run.py --ignore-files=test_autodoc_py35 -m '^[tT]est' {posargs} + {envpython} -Wall tests/run.py --ignore tests/py35 {posargs} sphinx-build -q -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html [testenv:pypy] @@ -48,5 +48,5 @@ deps= [testenv:py35] commands= - {envpython} -Wall tests/run.py -m '^[tT]est' {posargs} + {envpython} -Wall tests/run.py {posargs} sphinx-build -q -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html From f1d2a7ffaa1689e3e20c846b88969c2aadd284be Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Jan 2017 20:37:01 +0900 Subject: [PATCH 13/38] Ignore .cache/ --- .gitignore | 1 + Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0f9acd743..4472af150 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.swp .dir-locals.el +.cache/ .mypy_cache/ .ropeproject/ TAGS diff --git a/Makefile b/Makefile index 887f3e2a7..d1a0fd457 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,7 @@ clean-generated: clean-testfiles: rm -rf tests/build rm -rf .tox/ + rm -rf .cache/ clean-buildfiles: rm -rf build From 4153ee0d9d33074b09c7ee8b241dc1d61eb3f32f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Jan 2017 01:38:45 +0900 Subject: [PATCH 14/38] Fix Sphinx crashes on parallel build with an extension which raises unserializable exception --- CHANGES | 2 ++ sphinx/errors.py | 7 +++---- sphinx/util/parallel.py | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 3d2bc693e..beab1c718 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,8 @@ Bugs fixed * #3294: ``add_latex_package()`` make crashes non-LaTeX builders * The caption of table are rendered as invalid HTML (refs: #3287) * #3268: Sphinx crashes with requests package from Debian jessie +* #3284: Sphinx crashes on parallel build with an extension which raises + unserializable exception Release 1.5.1 (released Dec 13, 2016) diff --git a/sphinx/errors.py b/sphinx/errors.py index 5fb77a135..01f92a3cf 100644 --- a/sphinx/errors.py +++ b/sphinx/errors.py @@ -71,10 +71,9 @@ class SphinxParallelError(SphinxError): category = 'Sphinx parallel build error' - def __init__(self, orig_exc, traceback): - self.orig_exc = orig_exc + def __init__(self, message, traceback): + self.message = message self.traceback = traceback def __str__(self): - return traceback.format_exception_only( - self.orig_exc.__class__, self.orig_exc)[0].strip() + return self.message diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index bace0b5fd..4f4cd5c46 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -73,7 +73,8 @@ class ParallelTasks(object): ret = func(arg) pipe.send((False, ret)) except BaseException as err: - pipe.send((True, (err, traceback.format_exc()))) + errmsg = traceback.format_exception_only(err.__class__, err)[0].strip() + pipe.send((True, (errmsg, traceback.format_exc()))) def add_task(self, task_func, arg=None, result_func=None): tid = self._taskid From 40473290ad73fffb5994acf0e89e395a7d495a8f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Jan 2017 01:41:12 +0900 Subject: [PATCH 15/38] intersphinx: Update error message (refs: #3234) --- sphinx/ext/intersphinx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index b7cc849a4..f0592e4c8 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -221,8 +221,8 @@ def fetch_inventory(app, uri, inv): try: join = localuri and path.join or posixpath.join invdata = read_inventory(f, uri, join) - except ValueError: - raise ValueError('unknown or unsupported inventory version') + except ValueError as exc: + raise ValueError('unknown or unsupported inventory version: %r' % exc) except Exception as err: app.warn('intersphinx inventory %r not readable due to ' '%s: %s' % (inv, err.__class__.__name__, err)) From 83db04ae407d9c8703340af82efb23f016fcb044 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Jan 2017 01:52:21 +0900 Subject: [PATCH 16/38] Fix flake8 violation --- sphinx/errors.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sphinx/errors.py b/sphinx/errors.py index 01f92a3cf..01f29d7aa 100644 --- a/sphinx/errors.py +++ b/sphinx/errors.py @@ -10,8 +10,6 @@ :license: BSD, see LICENSE for details. """ -import traceback - class SphinxError(Exception): """ From 23d74ddbe6a1f79d15051c70663dbabef628be26 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Jan 2017 11:43:13 +0900 Subject: [PATCH 17/38] Downgrade jinja2 for test (refs: #3314) --- test-reqs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-reqs.txt b/test-reqs.txt index 1877886c1..247b22b73 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 +Jinja2>=2.3,<2.9 Pygments>=2.0 docutils>=0.11 snowballstemmer>=1.1 From 57dd53d903de6a89b26347ca0ca2e54ea1816134 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Jan 2017 11:58:14 +0900 Subject: [PATCH 18/38] Test pypy first to optimize Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0217e5dad..33c93038f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,12 @@ cache: directories: - $HOME/.cache/pip python: + - "pypy" - "2.7" - "3.4" - "3.5" - "3.6" - "nightly" - - "pypy" env: global: - TEST='-v --durations 25' From 175c6e66a6d3c23c3efecb27d22c44d9525db545 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 8 Jan 2017 14:35:44 +0900 Subject: [PATCH 19/38] Fix #978: `intersphinx_mapping` also allows a list as a parameter --- CHANGES | 1 + sphinx/ext/intersphinx.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index beab1c718..baf8a9bbc 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Features added * #3241: emit latex warning if buggy titlesec (ref #3210) * #3194: Refer the $MAKE environment variable to determine ``make`` command * Emit warning for nested numbered toctrees (refs: #3142) +* #978: `intersphinx_mapping` also allows a list as a parameter Bugs fixed ---------- diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index f0592e4c8..7513323ef 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -242,7 +242,7 @@ def load_mappings(app): cache = env.intersphinx_cache update = False for key, value in iteritems(app.config.intersphinx_mapping): - if isinstance(value, tuple): + if isinstance(value, (list, tuple)): # new format name, (uri, inv) = key, value if not isinstance(name, string_types): From 561abdd16454b1e635325d10951363bde2f4ed83 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sat, 7 Jan 2017 00:46:26 +0900 Subject: [PATCH 20/38] 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 21/38] 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 43eeb96e2f258583b7c3017f9affd5b8e2072e18 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 9 Jan 2017 02:21:31 +0900 Subject: [PATCH 22/38] Fix copy_asset_file() rewrote destination-filename --- sphinx/util/fileutil.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index 4375b7e61..b258c2039 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -41,7 +41,9 @@ def copy_asset_file(source, destination, context=None, renderer=None): renderer = SphinxRenderer() with codecs.open(source, 'r', encoding='utf-8') as fsrc: - with codecs.open(destination[:-2], 'w', encoding='utf-8') as fdst: + if destination.lower().endswith('_t'): + destination = destination[:-2] + with codecs.open(destination, 'w', encoding='utf-8') as fdst: fdst.write(renderer.render_string(fsrc.read(), context)) else: copyfile(source, destination) From b5530629a99d3e2b1060b9329a3134e0e6ebb10b Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Mon, 9 Jan 2017 17:41:47 +0900 Subject: [PATCH 23/38] 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 24/38] 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 25/38] 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 26/38] 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) From bb8dcb5ceea378368084567354f70fffcd5d5f42 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 12 Jan 2017 02:43:18 +0900 Subject: [PATCH 27/38] Fix code styles --- sphinx/builders/websupport.py | 2 +- sphinx/environment/managers/toctree.py | 2 +- sphinx/writers/latex.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/builders/websupport.py b/sphinx/builders/websupport.py index 1154b3419..2ed37a697 100644 --- a/sphinx/builders/websupport.py +++ b/sphinx/builders/websupport.py @@ -141,7 +141,7 @@ class WebSupportBuilder(PickleHTMLBuilder): # "show source" link if ctx.get('sourcename'): source_name = path.join(self.staticdir, - '_sources', os_path(ctx['sourcename'])) + '_sources', os_path(ctx['sourcename'])) ensuredir(path.dirname(source_name)) copyfile(self.env.doc2path(pagename), source_name) diff --git a/sphinx/environment/managers/toctree.py b/sphinx/environment/managers/toctree.py index 67fbfa7b6..2c8ae5300 100644 --- a/sphinx/environment/managers/toctree.py +++ b/sphinx/environment/managers/toctree.py @@ -417,7 +417,7 @@ class Toctree(EnvironmentManager): subnode.parent.remove(subnode) else: # recurse on visible children - self._toctree_prune(subnode, depth+1, maxdepth, collapse) + self._toctree_prune(subnode, depth+1, maxdepth, collapse) def assign_section_numbers(self): """Assign a section number to each heading under a numbered toctree.""" diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 70a9c6f68..7f1c8dc42 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1700,14 +1700,14 @@ class LaTeXTranslator(nodes.NodeVisitor): elif type == 'pair': p1, p2 = [self.encode(x) for x in split_into(2, 'pair', string)] self.body.append(r'\index{%s!%s%s}\index{%s!%s%s}' % - (p1, p2, m, p2, p1, m)) + (p1, p2, m, p2, p1, m)) elif type == 'triple': p1, p2, p3 = [self.encode(x) for x in split_into(3, 'triple', string)] self.body.append( r'\index{%s!%s %s%s}\index{%s!%s, %s%s}' r'\index{%s!%s %s%s}' % - (p1, p2, p3, m, p2, p3, p1, m, p3, p1, p2, m)) + (p1, p2, p3, m, p2, p3, p1, m, p3, p1, p2, m)) elif type == 'see': p1, p2 = [self.encode(x) for x in split_into(2, 'see', string)] self.body.append(r'\index{%s|see{%s}}' % (p1, p2)) From 60d4846e0fa4ec0e76ed6c963d13f74015939875 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 12 Jan 2017 10:07:05 +0900 Subject: [PATCH 28/38] Fix flake8 violations --- setup.cfg | 2 +- sphinx/application.py | 6 +++--- sphinx/builders/changes.py | 2 +- sphinx/builders/html.py | 6 +++--- sphinx/builders/htmlhelp.py | 10 +++++----- sphinx/builders/latex.py | 6 +++--- sphinx/builders/qthelp.py | 16 ++++++++-------- sphinx/builders/texinfo.py | 4 ++-- sphinx/directives/code.py | 6 +++--- sphinx/directives/other.py | 4 ++-- sphinx/domains/c.py | 6 +++--- sphinx/domains/cpp.py | 4 ++-- sphinx/domains/javascript.py | 2 +- sphinx/domains/python.py | 2 +- sphinx/domains/std.py | 4 ++-- sphinx/environment/__init__.py | 2 +- sphinx/environment/managers/toctree.py | 8 ++++---- sphinx/ext/autosummary/__init__.py | 12 ++++++------ sphinx/ext/doctest.py | 4 ++-- sphinx/ext/imgmath.py | 2 +- sphinx/ext/intersphinx.py | 12 ++++++------ sphinx/ext/pngmath.py | 2 +- sphinx/ext/todo.py | 2 +- sphinx/make_mode.py | 2 +- sphinx/pycode/__init__.py | 2 +- sphinx/pycode/nodes.py | 4 ++-- sphinx/quickstart.py | 8 ++++---- sphinx/roles.py | 4 ++-- sphinx/transforms/i18n.py | 4 ++-- sphinx/util/__init__.py | 12 ++++++------ sphinx/util/console.py | 4 ++-- sphinx/util/nodes.py | 8 ++++---- sphinx/util/osutil.py | 2 +- sphinx/util/parallel.py | 4 ++-- sphinx/util/stemmer.py | 16 ++++++++-------- sphinx/websupport/search/__init__.py | 2 +- sphinx/writers/html.py | 4 ++-- sphinx/writers/latex.py | 2 +- sphinx/writers/manpage.py | 2 +- sphinx/writers/texinfo.py | 12 ++++++------ sphinx/writers/text.py | 12 ++++++------ 41 files changed, 114 insertions(+), 114 deletions(-) diff --git a/setup.cfg b/setup.cfg index 73ac1e746..a833a1323 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,5 +25,5 @@ universal = 1 [flake8] max-line-length = 95 -ignore = E113,E116,E221,E226,E241,E251,E901 +ignore = E113,E116,E221,E241,E251,E901 exclude = .git,.tox,tests/*,build/*,sphinx/search/*,sphinx/pycode/pgen2/*,doc/ext/example*.py diff --git a/sphinx/application.py b/sphinx/application.py index f3bc381bc..f060843b4 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -484,7 +484,7 @@ class Sphinx(object): summary = bold(summary) for item in iterable: l += 1 - s = '%s[%3d%%] %s' % (summary, 100*l/length, + s = '%s[%3d%%] %s' % (summary, 100 * l / length, colorfunc(stringify_func(item))) if self.verbosity: s += '\n' @@ -660,9 +660,9 @@ class Sphinx(object): else: # ignore invalid keys for compatibility continue - setattr(translator, 'visit_'+node.__name__, visit) + setattr(translator, 'visit_' + node.__name__, visit) if depart: - setattr(translator, 'depart_'+node.__name__, depart) + setattr(translator, 'depart_' + node.__name__, depart) def add_enumerable_node(self, node, figtype, title_getter=None, **kwds): self.enumerable_nodes[node] = (figtype, title_getter) diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index a756742c9..eadbad09a 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -130,7 +130,7 @@ class ChangesBuilder(Builder): targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html' ensuredir(path.dirname(targetfn)) with codecs.open(targetfn, 'w', 'utf-8') as f: - text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines)) + text = ''.join(hl(i + 1, line) for (i, line) in enumerate(lines)) ctx = { 'filename': self.env.doc2path(docname, None), 'text': text diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 9160080c8..b9ab44676 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -498,7 +498,7 @@ class StandaloneHTMLBuilder(Builder): # additional pages from conf.py for pagename, template in self.config.html_additional_pages.items(): - self.info(' '+pagename, nonl=1) + self.info(' ' + pagename, nonl=1) self.handle_page(pagename, {}, template) # the search page @@ -953,7 +953,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): hashindex = refuri.find('#') if hashindex < 0: continue - hashindex = refuri.find('#', hashindex+1) + hashindex = refuri.find('#', hashindex + 1) if hashindex >= 0: refnode['refuri'] = fname + refuri[hashindex:] @@ -1059,7 +1059,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): # additional pages from conf.py for pagename, template in self.config.html_additional_pages.items(): - self.info(' '+pagename, nonl=1) + self.info(' ' + pagename, nonl=1) self.handle_page(pagename, {}, template) if self.config.html_use_opensearch: diff --git a/sphinx/builders/htmlhelp.py b/sphinx/builders/htmlhelp.py index 79268ab74..11c614b5a 100644 --- a/sphinx/builders/htmlhelp.py +++ b/sphinx/builders/htmlhelp.py @@ -208,12 +208,12 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): def build_hhx(self, outdir, outname): self.info('dumping stopword list...') - with self.open_file(outdir, outname+'.stp') as f: + with self.open_file(outdir, outname + '.stp') as f: for word in sorted(stopwords): print(word, file=f) self.info('writing project file...') - with self.open_file(outdir, outname+'.hhp') as f: + with self.open_file(outdir, outname + '.hhp') as f: f.write(project_template % { 'outname': outname, 'title': self.config.html_title, @@ -234,7 +234,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): file=f) self.info('writing TOC file...') - with self.open_file(outdir, outname+'.hhc') as f: + with self.open_file(outdir, outname + '.hhc') as f: f.write(contents_header) # special books f.write('
  • ' + object_sitemap % (self.config.html_short_title, @@ -259,7 +259,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): if ullevel != 0: f.write('
      \n') for subnode in node: - write_toc(subnode, ullevel+1) + write_toc(subnode, ullevel + 1) if ullevel != 0: f.write('
    \n') elif isinstance(node, addnodes.compact_paragraph): @@ -275,7 +275,7 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): self.info('writing index file...') index = self.env.create_index(self) - with self.open_file(outdir, outname+'.hhk') as f: + with self.open_file(outdir, outname + '.hhk') as f: f.write('