diff options
Diffstat (limited to 'sphinx/ext/doctest.py')
| -rw-r--r-- | sphinx/ext/doctest.py | 125 |
1 files changed, 57 insertions, 68 deletions
diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index f0c418ccf..23f3063cd 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ sphinx.ext.doctest ~~~~~~~~~~~~~~~~~~ @@ -9,33 +8,33 @@ :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -from __future__ import absolute_import -import codecs import doctest import re import sys import time +import warnings +from io import StringIO from os import path from docutils import nodes from docutils.parsers.rst import directives from packaging.specifiers import SpecifierSet, InvalidSpecifier from packaging.version import Version -from six import itervalues, StringIO, binary_type, text_type, PY2 import sphinx from sphinx.builders import Builder +from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.locale import __ -from sphinx.util import force_decode, logging +from sphinx.util import logging from sphinx.util.console import bold # type: ignore from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import set_source_info -from sphinx.util.osutil import fs_encoding, relpath +from sphinx.util.osutil import relpath if False: # For type annotation - from typing import Any, Callable, Dict, IO, Iterable, List, Optional, Sequence, Set, Tuple # NOQA + from typing import Any, Callable, Dict, IO, Iterable, List, Optional, Sequence, Set, Tuple, Type # NOQA from sphinx.application import Sphinx # NOQA logger = logging.getLogger(__name__) @@ -43,22 +42,16 @@ logger = logging.getLogger(__name__) blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE) doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE) -if PY2: - def doctest_encode(text, encoding): - # type: (str, unicode) -> unicode - if isinstance(text, text_type): - text = text.encode(encoding) - if text.startswith(codecs.BOM_UTF8): - text = text[len(codecs.BOM_UTF8):] - return text -else: - def doctest_encode(text, encoding): - # type: (unicode, unicode) -> unicode - return text + +def doctest_encode(text, encoding): + # type: (str, str) -> str + warnings.warn('doctest_encode() is deprecated.', + RemovedInSphinx40Warning) + return text def is_allowed_version(spec, version): - # type: (unicode, unicode) -> bool + # type: (str, str) -> bool """Check `spec` satisfies `version` or not. This obeys PEP-440 specifiers: @@ -113,7 +106,7 @@ class TestDirective(SphinxDirective): if not test: test = code code = doctestopt_re.sub('', code) - nodetype = nodes.literal_block + nodetype = nodes.literal_block # type: Type[nodes.TextElement] if self.name in ('testsetup', 'testcleanup') or 'hide' in self.options: nodetype = nodes.comment if self.arguments: @@ -126,9 +119,15 @@ class TestDirective(SphinxDirective): # only save if it differs from code node['test'] = test if self.name == 'doctest': - node['language'] = 'pycon' + if self.config.highlight_language in ('py', 'python'): + node['language'] = 'pycon' + else: + node['language'] = 'pycon3' # default elif self.name == 'testcode': - node['language'] = 'python' + if self.config.highlight_language in ('py', 'python'): + node['language'] = 'python' + else: + node['language'] = 'python3' # default elif self.name == 'testoutput': # don't try to highlight output node['language'] = 'none' @@ -203,9 +202,9 @@ parser = doctest.DocTestParser() # helper classes -class TestGroup(object): +class TestGroup: def __init__(self, name): - # type: (unicode) -> None + # type: (str) -> None self.name = name self.setup = [] # type: List[TestCode] self.tests = [] # type: List[List[TestCode]] @@ -230,23 +229,23 @@ class TestGroup(object): else: raise RuntimeError(__('invalid TestCode type')) - def __repr__(self): # type: ignore - # type: () -> unicode + def __repr__(self): + # type: () -> str return 'TestGroup(name=%r, setup=%r, cleanup=%r, tests=%r)' % ( self.name, self.setup, self.cleanup, self.tests) -class TestCode(object): +class TestCode: def __init__(self, code, type, filename, lineno, options=None): - # type: (unicode, unicode, Optional[str], int, Optional[Dict]) -> None + # type: (str, str, Optional[str], int, Optional[Dict]) -> None self.code = code self.type = type self.filename = filename self.lineno = lineno self.options = options or {} - def __repr__(self): # type: ignore - # type: () -> unicode + def __repr__(self): + # type: () -> str return 'TestCode(%r, %r, filename=%r, lineno=%r, options=%r)' % ( self.code, self.type, self.filename, self.lineno, self.options) @@ -258,7 +257,7 @@ class SphinxDocTestRunner(doctest.DocTestRunner): old_stdout = sys.stdout sys.stdout = string_io try: - res = doctest.DocTestRunner.summarize(self, verbose) + res = super().summarize(verbose) finally: sys.stdout = old_stdout out(string_io.getvalue()) @@ -266,7 +265,7 @@ class SphinxDocTestRunner(doctest.DocTestRunner): def _DocTestRunner__patched_linecache_getlines(self, filename, module_globals=None): - # type: (unicode, Any) -> Any + # type: (str, Any) -> Any # this is overridden from DocTestRunner adding the try-except below m = self._DocTestRunner__LINECACHE_FILENAME_RE.match(filename) # type: ignore if m and m.group('name') == self.test.name: @@ -317,41 +316,37 @@ class DocTestBuilder(Builder): date = time.strftime('%Y-%m-%d %H:%M:%S') - self.outfile = None # type: IO - self.outfile = codecs.open(path.join(self.outdir, 'output.txt'), # type: ignore - 'w', encoding='utf-8') + self.outfile = open(path.join(self.outdir, 'output.txt'), 'w', encoding='utf-8') self.outfile.write(('Results of doctest builder run on %s\n' '==================================%s\n') % (date, '=' * len(date))) def _out(self, text): - # type: (unicode) -> None + # type: (str) -> None logger.info(text, nonl=True) self.outfile.write(text) def _warn_out(self, text): - # type: (unicode) -> None + # type: (str) -> None if self.app.quiet or self.app.warningiserror: logger.warning(text) else: logger.info(text, nonl=True) - if isinstance(text, binary_type): - text = force_decode(text, None) self.outfile.write(text) def get_target_uri(self, docname, typ=None): - # type: (unicode, unicode) -> unicode + # type: (str, str) -> str return '' def get_outdated_docs(self): - # type: () -> Set[unicode] + # type: () -> Set[str] return self.env.found_docs def finish(self): # type: () -> None # write executive summary def s(v): - # type: (int) -> unicode + # type: (int) -> str return v != 1 and 's' or '' repl = (self.total_tries, s(self.total_tries), self.total_failures, s(self.total_failures), @@ -371,7 +366,7 @@ Doctest summary self.app.statuscode = 1 def write(self, build_docnames, updated_docnames, method='update'): - # type: (Iterable[unicode], Sequence[unicode], unicode) -> None + # type: (Iterable[str], Sequence[str], str) -> None if build_docnames is None: build_docnames = sorted(self.env.all_docs) @@ -382,7 +377,7 @@ Doctest summary self.test_doc(docname, doctree) def get_filename_for_node(self, node, docname): - # type: (nodes.Node, unicode) -> str + # type: (nodes.Node, str) -> str """Try to get the file which actually contains the doctest, not the filename of the document it's included in.""" try: @@ -390,8 +385,6 @@ Doctest summary .rsplit(':docstring of ', maxsplit=1)[0] except Exception: filename = self.env.doc2path(docname, base=None) - if PY2: - return filename.encode(fs_encoding) return filename @staticmethod @@ -412,8 +405,8 @@ Doctest summary return None def test_doc(self, docname, doctree): - # type: (unicode, nodes.Node) -> None - groups = {} # type: Dict[unicode, TestGroup] + # type: (str, nodes.Node) -> None + groups = {} # type: Dict[str, TestGroup] add_to_all_groups = [] self.setup_runner = SphinxDocTestRunner(verbose=False, optionflags=self.opt) @@ -436,7 +429,8 @@ Doctest summary # type: (nodes.Node) -> bool return isinstance(node, (nodes.literal_block, nodes.comment)) \ and 'testnodetype' in node - for node in doctree.traverse(condition): + + for node in doctree.traverse(condition): # type: nodes.Element source = node['test'] if 'test' in node else node.astext() filename = self.get_filename_for_node(node, docname) line_number = self.get_line_number(node) @@ -456,24 +450,24 @@ Doctest summary groups[groupname] = TestGroup(groupname) groups[groupname].add_code(code) for code in add_to_all_groups: - for group in itervalues(groups): + for group in groups.values(): group.add_code(code) if self.config.doctest_global_setup: code = TestCode(self.config.doctest_global_setup, 'testsetup', filename=None, lineno=0) - for group in itervalues(groups): + for group in groups.values(): group.add_code(code, prepend=True) if self.config.doctest_global_cleanup: code = TestCode(self.config.doctest_global_cleanup, 'testcleanup', filename=None, lineno=0) - for group in itervalues(groups): + for group in groups.values(): group.add_code(code) if not groups: return self._out('\nDocument: %s\n----------%s\n' % (docname, '-' * len(docname))) - for group in itervalues(groups): + for group in groups.values(): self.test_group(group) # Separately count results from setup code res_f, res_t = self.setup_runner.summarize(self._out, verbose=False) @@ -490,7 +484,7 @@ Doctest summary self.cleanup_tries += res_t def compile(self, code, name, type, flags, dont_inherit): - # type: (unicode, unicode, unicode, Any, bool) -> Any + # type: (str, str, str, Any, bool) -> Any return compile(code, name, self.type, flags, dont_inherit) def test_group(self, group): @@ -501,9 +495,8 @@ Doctest summary # type: (Any, List[TestCode], Any) -> bool examples = [] for testcode in testcodes: - examples.append(doctest.Example( # type: ignore - doctest_encode(testcode.code, self.env.config.source_encoding), '', # type: ignore # NOQA - lineno=testcode.lineno)) + example = doctest.Example(testcode.code, '', lineno=testcode.lineno) + examples.append(example) if not examples: return True # simulate a doctest with the code @@ -528,9 +521,8 @@ Doctest summary if len(code) == 1: # ordinary doctests (code/output interleaved) try: - test = parser.get_doctest( # type: ignore - doctest_encode(code[0].code, self.env.config.source_encoding), {}, # type: ignore # NOQA - group.name, code[0].filename, code[0].lineno) + test = parser.get_doctest(code[0].code, {}, group.name, # type: ignore + code[0].filename, code[0].lineno) except Exception: logger.warning(__('ignoring invalid doctest code: %r'), code[0].code, location=(code[0].filename, code[0].lineno)) @@ -555,12 +547,9 @@ Doctest summary exc_msg = m.group('msg') else: exc_msg = None - example = doctest.Example( # type: ignore - doctest_encode(code[0].code, self.env.config.source_encoding), output, # type: ignore # NOQA - exc_msg=exc_msg, - lineno=code[0].lineno, - options=options) - test = doctest.DocTest([example], {}, group.name, # type: ignore + example = doctest.Example(code[0].code, output, exc_msg=exc_msg, + lineno=code[0].lineno, options=options) + test = doctest.DocTest([example], {}, group.name, code[0].filename, code[0].lineno, None) self.type = 'exec' # multiple statements again # DocTest.__init__ copies the globs namespace, which we don't want @@ -573,7 +562,7 @@ Doctest summary def setup(app): - # type: (Sphinx) -> Dict[unicode, Any] + # type: (Sphinx) -> Dict[str, Any] app.add_directive('testsetup', TestsetupDirective) app.add_directive('testcleanup', TestcleanupDirective) app.add_directive('doctest', DoctestDirective) |
