Merge pull request #3339 from marco-buttu/master

Added pyversion option for doctest (issue 3303)
This commit is contained in:
Takeshi KOMIYA 2017-01-28 20:34:30 +09:00 committed by GitHub
commit 0aa6ff3681
6 changed files with 99 additions and 5 deletions

View File

@ -21,6 +21,7 @@ Other contributors, listed alphabetically, are:
* Henrique Bastos -- SVG support for graphviz extension
* Daniel Bültmann -- todo extension
* Jean-François Burnol -- LaTeX improvements
* Marco Buttu -- doctest extension (pyversion option)
* Etienne Desautels -- apidoc module
* Michael Droettboom -- inheritance_diagram extension
* Charles Duffy -- original graphviz extension

View File

@ -43,6 +43,7 @@ Features added
* C++, add ``:tparam-line-spec:`` option to templated declarations.
When specified, each template parameter will be rendered on a separate line.
* #3359: Allow sphinx.js in a user locale dir to override sphinx.js from Sphinx
* #3303: Add ``:pyversion:`` option to the doctest directive.
Bugs fixed
----------

View File

@ -63,7 +63,7 @@ a comma-separated list of group names.
default set of flags is specified by the :confval:`doctest_default_flags`
configuration variable.
This directive supports two options:
This directive supports three options:
* ``hide``, a flag option, hides the doctest block in other builders. By
default it is shown as a highlighted doctest block.
@ -73,6 +73,19 @@ a comma-separated list of group names.
explicit flags per example, with doctest comments, but they will show up in
other builders too.)
* ``pyversion``, a string option, can be used to specify the required Python
version for the example to be tested. For instance, in the following case
the example will be tested only for Python versions greather than 3.3::
.. doctest::
:pyversion: > 3.3
The supported operands are ``<``, ``<=``, ``==``, ``>=``, ``>``, and
comparison is performed by `distutils.version.LooseVersion
<https://www.python.org/dev/peps/pep-0386/#distutils>`__.
.. versionadded:: 1.6
Note that like with standard doctests, you have to use ``<BLANKLINE>`` to
signal a blank line in the expected output. The ``<BLANKLINE>`` is removed
when building presentation output (HTML, LaTeX etc.).

View File

@ -15,10 +15,12 @@ import re
import sys
import time
import codecs
import platform
from os import path
import doctest
from six import itervalues, StringIO, binary_type, text_type, PY2
from distutils.version import LooseVersion
from docutils import nodes
from docutils.parsers.rst import Directive, directives
@ -29,6 +31,7 @@ from sphinx.util import force_decode, logging
from sphinx.util.nodes import set_source_info
from sphinx.util.console import bold # type: ignore
from sphinx.util.osutil import fs_encoding
from sphinx.locale import _
if False:
# For type annotation
@ -54,6 +57,29 @@ else:
return text
def compare_version(ver1, ver2, operand):
"""Compare `ver1` to `ver2`, relying on `operand`.
Some examples:
>>> compare_version('3.3', '3.5', '<=')
True
>>> compare_version('3.3', '3.2', '<=')
False
>>> compare_version('3.3a0', '3.3', '<=')
True
"""
if operand not in ('<=', '<', '==', '>=', '>'):
raise ValueError("'%s' is not a valid operand.")
v1 = LooseVersion(ver1)
v2 = LooseVersion(ver2)
return ((operand == '<=' and (v1 <= v2)) or
(operand == '<' and (v1 < v2)) or
(operand == '==' and (v1 == v2)) or
(operand == '>=' and (v1 >= v2)) or
(operand == '>' and (v1 > v2)))
# set up the necessary directives
class TestDirective(Directive):
@ -101,12 +127,32 @@ class TestDirective(Directive):
# parse doctest-like output comparison flags
option_strings = self.options['options'].replace(',', ' ').split()
for option in option_strings:
if (option[0] not in '+-' or option[1:] not in
doctest.OPTIONFLAGS_BY_NAME): # type: ignore
# XXX warn?
prefix, option_name = option[0], option[1:]
if prefix not in '+-': # type: ignore
self.state.document.reporter.warning(
_("missing '+' or '-' in '%s' option.") % option,
line=self.lineno)
continue
if option_name not in doctest.OPTIONFLAGS_BY_NAME: # type: ignore
self.state.document.reporter.warning(
_("'%s' is not a valid option.") % option_name,
line=self.lineno)
continue
flag = doctest.OPTIONFLAGS_BY_NAME[option[1:]] # type: ignore
node['options'][flag] = (option[0] == '+')
if self.name == 'doctest' and 'pyversion' in self.options:
try:
option = self.options['pyversion']
# :pyversion: >= 3.6 --> operand='>=', option_version='3.6'
operand, option_version = [item.strip() for item in option.split()]
running_version = platform.python_version()
if not compare_version(running_version, option_version, operand):
flag = doctest.OPTIONFLAGS_BY_NAME['SKIP']
node['options'][flag] = True # Skip the test
except ValueError:
self.state.document.reporter.warning(
_("'%s' is not a valid pyversion option") % option,
line=self.lineno)
return [node]
@ -122,12 +168,14 @@ class DoctestDirective(TestDirective):
option_spec = {
'hide': directives.flag,
'options': directives.unchanged,
'pyversion': directives.unchanged_required,
}
class TestcodeDirective(TestDirective):
option_spec = {
'hide': directives.flag,
'pyversion': directives.unchanged_required,
}
@ -135,6 +183,7 @@ class TestoutputDirective(TestDirective):
option_spec = {
'hide': directives.flag,
'options': directives.unchanged,
'pyversion': directives.unchanged_required,
}

View File

@ -69,7 +69,7 @@ Special directives
>>> squared(2)
4
* options for testcode/testoutput blocks
* options for doctest/testcode/testoutput blocks
.. testcode::
:hide:
@ -82,6 +82,20 @@ Special directives
Output text.
.. doctest::
:pyversion: >= 2.0
>>> a = 3
>>> a
3
.. doctest::
:pyversion: < 2.0
>>> a = 3
>>> a
4
* grouping
.. testsetup:: group1

View File

@ -9,6 +9,7 @@
:license: BSD, see LICENSE for details.
"""
import pytest
from sphinx.ext.doctest import compare_version
cleanup_called = 0
@ -25,6 +26,21 @@ def test_build(app, status, warning):
assert cleanup_called == 3, 'testcleanup did not get executed enough times'
def test_compare_version():
assert compare_version('3.3', '3.4', '<') is True
assert compare_version('3.3', '3.2', '<') is False
assert compare_version('3.3', '3.4', '<=') is True
assert compare_version('3.3', '3.2', '<=') is False
assert compare_version('3.3', '3.3', '==') is True
assert compare_version('3.3', '3.4', '==') is False
assert compare_version('3.3', '3.2', '>=') is True
assert compare_version('3.3', '3.4', '>=') is False
assert compare_version('3.3', '3.2', '>') is True
assert compare_version('3.3', '3.4', '>') is False
with pytest.raises(ValueError):
compare_version('3.3', '3.4', '+')
def cleanup_call():
global cleanup_called
cleanup_called += 1