You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import print_function
import re
import sys
import os
import codecs
import doctest
from nose.util import tolist, anyp
from nose.plugins.base import Plugin
from nose.suite import ContextList
from nose.plugins.doctests import Doctest, log, DocFileCase
ALLOW_UNICODE = doctest.register_optionflag('ALLOW_UNICODE')
class _UnicodeOutputChecker(doctest.OutputChecker):
_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
def _remove_u_prefixes(self, txt):
return re.sub(self._literal_re, r'\1\2', txt)
def check_output(self, want, got, optionflags):
res = doctest.OutputChecker.check_output(self, want, got, optionflags)
if res:
return True
if not (optionflags & ALLOW_UNICODE):
return False
# ALLOW_UNICODE is active and want != got
cleaned_want = self._remove_u_prefixes(want)
cleaned_got = self._remove_u_prefixes(got)
res = doctest.OutputChecker.check_output(
self, cleaned_want, cleaned_got, optionflags
)
return res
_checker = _UnicodeOutputChecker()
class DoctestPluginHelper(object):
"""
This mixin adds print_function future import to all test cases.
It also adds support for:
'#doctest +ALLOW_UNICODE' option that
makes DocTestCase think u'foo' == 'foo'.
'#doctest doctestencoding=utf-8' option that
changes the encoding of doctest files
"""
OPTION_BY_NAME = ('doctestencoding',)
def loadTestsFromFileUnicode(self, filename):
if self.extension and anyp(filename.endswith, self.extension):
name = os.path.basename(filename)
dh = codecs.open(filename, 'r', self.options.get('doctestencoding'))
try:
doc = dh.read()
finally:
dh.close()
fixture_context = None
globs = {'__file__': filename}
if self.fixtures:
base, ext = os.path.splitext(name)
dirname = os.path.dirname(filename)
sys.path.append(dirname)
fixt_mod = base + self.fixtures
try:
fixture_context = __import__(fixt_mod, globals(), locals(), ["nop"])
except ImportError as e:
log.debug("Could not import %s: %s (%s)", fixt_mod, e, sys.path)
log.debug("Fixture module %s resolved to %s", fixt_mod, fixture_context)
if hasattr(fixture_context, 'globs'):
globs = fixture_context.globs(globs)
parser = doctest.DocTestParser()
test = parser.get_doctest(
doc, globs=globs, name=name, filename=filename, lineno=0
)
if test.examples:
case = DocFileCase(
test,
optionflags=self.optionflags,
setUp=getattr(fixture_context, 'setup_test', None),
tearDown=getattr(fixture_context, 'teardown_test', None),
result_var=self.doctest_result_var,
)
if fixture_context:
yield ContextList((case,), context=fixture_context)
else:
yield case
else:
yield False # no tests to load
def loadTestsFromFile(self, filename):
cases = self.loadTestsFromFileUnicode(filename)
for case in cases:
if isinstance(case, ContextList):
yield ContextList([self._patchTestCase(c) for c in case], case.context)
else:
yield self._patchTestCase(case)
def loadTestsFromModule(self, module):
"""Load doctests from the module.
"""
for suite in super(DoctestPluginHelper, self).loadTestsFromModule(module):
cases = [self._patchTestCase(case) for case in suite._get_tests()]
yield self.suiteClass(cases, context=module, can_split=False)
def _patchTestCase(self, case):
if case:
case._dt_test.globs['print_function'] = print_function
case._dt_checker = _checker
return case
def configure(self, options, config):
# it is overriden in order to fix doctest options discovery
Plugin.configure(self, options, config)
self.doctest_result_var = options.doctest_result_var
self.doctest_tests = options.doctest_tests
self.extension = tolist(options.doctestExtension)
self.fixtures = options.doctestFixtures
self.finder = doctest.DocTestFinder()
# super(DoctestPluginHelper, self).configure(options, config)
self.optionflags = 0
self.options = {}
if options.doctestOptions:
stroptions = ",".join(options.doctestOptions).split(',')
for stroption in stroptions:
try:
if stroption.startswith('+'):
self.optionflags |= doctest.OPTIONFLAGS_BY_NAME[stroption[1:]]
continue
elif stroption.startswith('-'):
self.optionflags &= ~doctest.OPTIONFLAGS_BY_NAME[stroption[1:]]
continue
try:
key, value = stroption.split('=')
except ValueError:
pass
else:
if not key in self.OPTION_BY_NAME:
raise ValueError()
self.options[key] = value
continue
except (AttributeError, ValueError, KeyError):
raise ValueError("Unknown doctest option {}".format(stroption))
else:
raise ValueError(
"Doctest option is not a flag or a key/value pair: {} ".format(
stroption
)
)
class DoctestFix(DoctestPluginHelper, Doctest):
pass