From 19298dec337d20526702109e3eeb7629dccd1d50 Mon Sep 17 00:00:00 2001 From: Marco Buttu Date: Sat, 14 Jan 2017 18:57:19 +0100 Subject: [PATCH] Added pyversion option for doctest (issue 3303) --- sphinx/ext/doctest.py | 58 +++++++++++++++++++++++---------------- tests/test_ext_doctest.py | 19 +++++++++++++ 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index b71fa2569..f645283a2 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -20,12 +20,11 @@ from os import path import doctest from six import itervalues, StringIO, binary_type, text_type, PY2 +from distutils.version import StrictVersion from docutils import nodes from docutils.parsers.rst import Directive, directives -from distutils.version import StrictVersion as V - import sphinx from sphinx.builders import Builder from sphinx.util import force_decode, logging @@ -107,8 +106,8 @@ class TestDirective(Directive): if (option[0] not in '+-' or option[1:] not in doctest.OPTIONFLAGS_BY_NAME): # type: ignore self.state.document.reporter.warning( - "missing '+' or '-' in '%s' option." % option, - line=self.lineno) + "missing '+' or '-' in '%s' option." % option, + line=self.lineno) continue flag = doctest.OPTIONFLAGS_BY_NAME[option[1:]] # type: ignore node['options'][flag] = (option[0] == '+') @@ -116,32 +115,43 @@ class TestDirective(Directive): try: option = self.options['pyversion'] option_strings = option.split() - # :pyversion: >= 3.6 --> op='>=', version='3.6' + # :pyversion: >= 3.6 --> operand='>=', version='3.6' operand, version = [item.strip() for item in option_strings] - operands = ('<=', '<', '==', '>=', '>') - if operand not in operands: - self.state.document.reporter.warning( - "'%s' is not a valid pyversion operand.\n" - "Avaliable operands: %s" % (operand, operands), - line=self.lineno) - else: - rv = V(platform.python_version()) # Running version - sv = V(version) # Specified version - skip = ((operand == '<=' and not (rv <= sv)) or - (operand == '<' and not (rv < sv)) or - (operand == '==' and not (rv == sv)) or - (operand == '>=' and not (rv >= sv)) or - (operand == '>' and not (rv > sv))) - if skip: - flag = doctest.OPTIONFLAGS_BY_NAME['SKIP'] - node['options'][flag] = True + if not self.proper_pyversion(operand, version): + 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 value" % option, - line=self.lineno) + "'%s' is not a valid pyversion value" % option, + line=self.lineno) return [node] + def proper_pyversion(self, operand, version): + """Compare `version` to the Python version, relying on `operand`. + + This function is meant to be used to evaluate the doctest :pyversion: + option. For instance, if the doctest directive provides the option + :pyversion: >= 3.3, then we have to check if the running Python version + is greather or equal to 3.3. In that case, operand will be the string + '>=' and version the string '3.3'. proper_pyversion() will return True + if the running Python version is >= 3.3, False otherwise. + """ + operands = ('<=', '<', '==', '>=', '>') + if operand not in operands: + self.document.reporter.warning( + "'%s' is not a valid pyversion operand.\n" + "Avaliable operands: %s" % (operand, operands), + line=self.lineno) + return True # Be defensive, making the doctest to be executed + rv = StrictVersion(platform.python_version()) # Running version + sv = StrictVersion(version) # Specified version + return ((operand == '<=' and (rv <= sv)) or + (operand == '<' and (rv < sv)) or + (operand == '==' and (rv == sv)) or + (operand == '>=' and (rv >= sv)) or + (operand == '>' and (rv > sv))) + class TestsetupDirective(TestDirective): option_spec = {} # type: Dict diff --git a/tests/test_ext_doctest.py b/tests/test_ext_doctest.py index 6b17f2ed7..34ca3d027 100644 --- a/tests/test_ext_doctest.py +++ b/tests/test_ext_doctest.py @@ -8,7 +8,9 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import platform import pytest +from sphinx.ext.doctest import TestDirective as _TestDirective cleanup_called = 0 @@ -25,6 +27,23 @@ def test_build(app, status, warning): assert cleanup_called == 3, 'testcleanup did not get executed enough times' +def test_pyversion(monkeypatch): + def python_version(): + return '3.3' + monkeypatch.setattr(platform, 'python_version', python_version) + td = _TestDirective(*([None] * 9)) + assert td.proper_pyversion('<', '3.4') is True + assert td.proper_pyversion('<', '3.2') is False + assert td.proper_pyversion('<=', '3.4') is True + assert td.proper_pyversion('<=', '3.3') is True + assert td.proper_pyversion('==', '3.3') is True + assert td.proper_pyversion('>=', '3.4') is False + assert td.proper_pyversion('>=', '3.2') is True + assert td.proper_pyversion('>', '3.4') is False + assert td.proper_pyversion('>', '3.2') is True + assert td.proper_pyversion('>', '3.3a0') is True + + def cleanup_call(): global cleanup_called cleanup_called += 1