summaryrefslogtreecommitdiff
path: root/sphinx/ext/doctest.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/ext/doctest.py')
-rw-r--r--sphinx/ext/doctest.py125
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)