1"""Use the Doctest plugin with ``--with-doctest`` or the NOSE_WITH_DOCTEST 2environment variable to enable collection and execution of :mod:`doctests 3<doctest>`. Because doctests are usually included in the tested package 4(instead of being grouped into packages or modules of their own), nose only 5looks for them in the non-test packages it discovers in the working directory. 6 7Doctests may also be placed into files other than python modules, in which 8case they can be collected and executed by using the ``--doctest-extension`` 9switch or NOSE_DOCTEST_EXTENSION environment variable to indicate which file 10extension(s) to load. 11 12When loading doctests from non-module files, use the ``--doctest-fixtures`` 13switch to specify how to find modules containing fixtures for the tests. A 14module name will be produced by appending the value of that switch to the base 15name of each doctest file loaded. For example, a doctest file "widgets.rst" 16with the switch ``--doctest_fixtures=_fixt`` will load fixtures from the module 17``widgets_fixt.py``. 18 19A fixtures module may define any or all of the following functions: 20 21* setup([module]) or setup_module([module]) 22 23 Called before the test runs. You may raise SkipTest to skip all tests. 24 25* teardown([module]) or teardown_module([module]) 26 27 Called after the test runs, if setup/setup_module did not raise an 28 unhandled exception. 29 30* setup_test(test) 31 32 Called before the test. NOTE: the argument passed is a 33 doctest.DocTest instance, *not* a unittest.TestCase. 34 35* teardown_test(test) 36 37 Called after the test, if setup_test did not raise an exception. NOTE: the 38 argument passed is a doctest.DocTest instance, *not* a unittest.TestCase. 39 40Doctests are run like any other test, with the exception that output 41capture does not work; doctest does its own output capture while running a 42test. 43 44.. note :: 45 46 See :doc:`../doc_tests/test_doctest_fixtures/doctest_fixtures` for 47 additional documentation and examples. 48 49""" 50 51 52import logging 53import os 54import sys 55import unittest 56from inspect import getmodule 57from nose.plugins.base import Plugin 58from nose.suite import ContextList 59from nose.util import anyp, getpackage, test_address, resolve_name, \ 60 src, tolist, isproperty 61try: 62 from io import StringIO 63except ImportError: 64 from io import StringIO 65import sys 66import builtins as builtin_mod 67 68log = logging.getLogger(__name__) 69 70try: 71 import doctest 72 doctest.DocTestCase 73 # system version of doctest is acceptable, but needs a monkeypatch 74except (ImportError, AttributeError): 75 # system version is too old 76 import nose.ext.dtcompat as doctest 77 78 79# 80# Doctest and coverage don't get along, so we need to create 81# a monkeypatch that will replace the part of doctest that 82# interferes with coverage reports. 83# 84# The monkeypatch is based on this zope patch: 85# http://svn.zope.org/Zope3/trunk/src/zope/testing/doctest.py?rev=28679&r1=28703&r2=28705 86# 87_orp = doctest._OutputRedirectingPdb 88 89class NoseOutputRedirectingPdb(_orp): 90 def __init__(self, out): 91 self.__debugger_used = False 92 _orp.__init__(self, out) 93 94 def set_trace(self): 95 self.__debugger_used = True 96 _orp.set_trace(self, sys._getframe().f_back) 97 98 def set_continue(self): 99 # Calling set_continue unconditionally would break unit test 100 # coverage reporting, as Bdb.set_continue calls sys.settrace(None). 101 if self.__debugger_used: 102 _orp.set_continue(self) 103doctest._OutputRedirectingPdb = NoseOutputRedirectingPdb 104 105 106class DoctestSuite(unittest.TestSuite): 107 """ 108 Doctest suites are parallelizable at the module or file level only, 109 since they may be attached to objects that are not individually 110 addressable (like properties). This suite subclass is used when 111 loading doctests from a module to ensure that behavior. 112 113 This class is used only if the plugin is not fully prepared; 114 in normal use, the loader's suiteClass is used. 115 116 """ 117 can_split = False 118 119 def __init__(self, tests=(), context=None, can_split=False): 120 self.context = context 121 self.can_split = can_split 122 unittest.TestSuite.__init__(self, tests=tests) 123 124 def address(self): 125 return test_address(self.context) 126 127 def __iter__(self): 128 # 2.3 compat 129 return iter(self._tests) 130 131 def __str__(self): 132 return str(self._tests) 133 134 135class Doctest(Plugin): 136 """ 137 Activate doctest plugin to find and run doctests in non-test modules. 138 """ 139 extension = None 140 suiteClass = DoctestSuite 141 142 def options(self, parser, env): 143 """Register commmandline options. 144 """ 145 Plugin.options(self, parser, env) 146 parser.add_option('--doctest-tests', action='store_true', 147 dest='doctest_tests', 148 default=env.get('NOSE_DOCTEST_TESTS'), 149 help="Also look for doctests in test modules. " 150 "Note that classes, methods and functions should " 151 "have either doctests or non-doctest tests, " 152 "not both. [NOSE_DOCTEST_TESTS]") 153 parser.add_option('--doctest-extension', action="append", 154 dest="doctestExtension", 155 metavar="EXT", 156 help="Also look for doctests in files with " 157 "this extension [NOSE_DOCTEST_EXTENSION]") 158 parser.add_option('--doctest-result-variable', 159 dest='doctest_result_var', 160 default=env.get('NOSE_DOCTEST_RESULT_VAR'), 161 metavar="VAR", 162 help="Change the variable name set to the result of " 163 "the last interpreter command from the default '_'. " 164 "Can be used to avoid conflicts with the _() " 165 "function used for text translation. " 166 "[NOSE_DOCTEST_RESULT_VAR]") 167 parser.add_option('--doctest-fixtures', action="store", 168 dest="doctestFixtures", 169 metavar="SUFFIX", 170 help="Find fixtures for a doctest file in module " 171 "with this name appended to the base name " 172 "of the doctest file") 173 parser.add_option('--doctest-options', action="append", 174 dest="doctestOptions", 175 metavar="OPTIONS", 176 help="Specify options to pass to doctest. " + 177 "Eg. '+ELLIPSIS,+NORMALIZE_WHITESPACE'") 178 # Set the default as a list, if given in env; otherwise 179 # an additional value set on the command line will cause 180 # an error. 181 env_setting = env.get('NOSE_DOCTEST_EXTENSION') 182 if env_setting is not None: 183 parser.set_defaults(doctestExtension=tolist(env_setting)) 184 185 def configure(self, options, config): 186 """Configure plugin. 187 """ 188 Plugin.configure(self, options, config) 189 self.doctest_result_var = options.doctest_result_var 190 self.doctest_tests = options.doctest_tests 191 self.extension = tolist(options.doctestExtension) 192 self.fixtures = options.doctestFixtures 193 self.finder = doctest.DocTestFinder() 194 self.optionflags = 0 195 if options.doctestOptions: 196 flags = ",".join(options.doctestOptions).split(',') 197 for flag in flags: 198 if not flag or flag[0] not in '+-': 199 raise ValueError( 200 "Must specify doctest options with starting " + 201 "'+' or '-'. Got %s" % (flag,)) 202 mode, option_name = flag[0], flag[1:] 203 option_flag = doctest.OPTIONFLAGS_BY_NAME.get(option_name) 204 if not option_flag: 205 raise ValueError("Unknown doctest option %s" % 206 (option_name,)) 207 if mode == '+': 208 self.optionflags |= option_flag 209 elif mode == '-': 210 self.optionflags &= ~option_flag 211 212 def prepareTestLoader(self, loader): 213 """Capture loader's suiteClass. 214 215 This is used to create test suites from doctest files. 216 217 """ 218 self.suiteClass = loader.suiteClass 219 220 def loadTestsFromModule(self, module): 221 """Load doctests from the module. 222 """ 223 log.debug("loading from %s", module) 224 if not self.matches(module.__name__): 225 log.debug("Doctest doesn't want module %s", module) 226 return 227 try: 228 tests = self.finder.find(module) 229 except AttributeError: 230 log.exception("Attribute error loading from %s", module) 231 # nose allows module.__test__ = False; doctest does not and throws 232 # AttributeError 233 return 234 if not tests: 235 log.debug("No tests found in %s", module) 236 return 237 tests.sort() 238 module_file = src(module.__file__) 239 # FIXME this breaks the id plugin somehow (tests probably don't 240 # get wrapped in result proxy or something) 241 cases = [] 242 for test in tests: 243 if not test.examples: 244 continue 245 if not test.filename: 246 test.filename = module_file 247 cases.append(DocTestCase(test, 248 optionflags=self.optionflags, 249 result_var=self.doctest_result_var)) 250 if cases: 251 yield self.suiteClass(cases, context=module, can_split=False) 252 253 def loadTestsFromFile(self, filename): 254 """Load doctests from the file. 255 256 Tests are loaded only if filename's extension matches 257 configured doctest extension. 258 259 """ 260 if self.extension and anyp(filename.endswith, self.extension): 261 name = os.path.basename(filename) 262 dh = open(filename) 263 try: 264 doc = dh.read() 265 finally: 266 dh.close() 267 268 fixture_context = None 269 globs = {'__file__': filename} 270 if self.fixtures: 271 base, ext = os.path.splitext(name) 272 dirname = os.path.dirname(filename) 273 sys.path.append(dirname) 274 fixt_mod = base + self.fixtures 275 try: 276 fixture_context = __import__( 277 fixt_mod, globals(), locals(), ["nop"]) 278 except ImportError as e: 279 log.debug( 280 "Could not import %s: %s (%s)", fixt_mod, e, sys.path) 281 log.debug("Fixture module %s resolved to %s", 282 fixt_mod, fixture_context) 283 if hasattr(fixture_context, 'globs'): 284 globs = fixture_context.globs(globs) 285 parser = doctest.DocTestParser() 286 test = parser.get_doctest( 287 doc, globs=globs, name=name, 288 filename=filename, lineno=0) 289 if test.examples: 290 case = DocFileCase( 291 test, 292 optionflags=self.optionflags, 293 setUp=getattr(fixture_context, 'setup_test', None), 294 tearDown=getattr(fixture_context, 'teardown_test', None), 295 result_var=self.doctest_result_var) 296 if fixture_context: 297 yield ContextList((case,), context=fixture_context) 298 else: 299 yield case 300 else: 301 yield False # no tests to load 302 303 def makeTest(self, obj, parent): 304 """Look for doctests in the given object, which will be a 305 function, method or class. 306 """ 307 name = getattr(obj, '__name__', 'Unnammed %s' % type(obj)) 308 doctests = self.finder.find(obj, module=getmodule(parent), name=name) 309 if doctests: 310 for test in doctests: 311 if len(test.examples) == 0: 312 continue 313 yield DocTestCase(test, obj=obj, optionflags=self.optionflags, 314 result_var=self.doctest_result_var) 315 316 def matches(self, name): 317 # FIXME this seems wrong -- nothing is ever going to 318 # fail this test, since we're given a module NAME not FILE 319 if name == '__init__.py': 320 return False 321 # FIXME don't think we need include/exclude checks here? 322 return ((self.doctest_tests or not self.conf.testMatch.search(name) 323 or (self.conf.include 324 and [_f for _f in [inc.search(name) 325 for inc in self.conf.include] if _f])) 326 and (not self.conf.exclude 327 or not [_f for _f in [exc.search(name) 328 for exc in self.conf.exclude] if _f])) 329 330 def wantFile(self, file): 331 """Override to select all modules and any file ending with 332 configured doctest extension. 333 """ 334 # always want .py files 335 if file.endswith('.py'): 336 return True 337 # also want files that match my extension 338 if (self.extension 339 and anyp(file.endswith, self.extension) 340 and (not self.conf.exclude 341 or not [_f for _f in [exc.search(file) 342 for exc in self.conf.exclude] if _f])): 343 return True 344 return None 345 346 347class DocTestCase(doctest.DocTestCase): 348 """Overrides DocTestCase to 349 provide an address() method that returns the correct address for 350 the doctest case. To provide hints for address(), an obj may also 351 be passed -- this will be used as the test object for purposes of 352 determining the test address, if it is provided. 353 """ 354 def __init__(self, test, optionflags=0, setUp=None, tearDown=None, 355 checker=None, obj=None, result_var='_'): 356 self._result_var = result_var 357 self._nose_obj = obj 358 super(DocTestCase, self).__init__( 359 test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, 360 checker=checker) 361 362 def address(self): 363 if self._nose_obj is not None: 364 return test_address(self._nose_obj) 365 obj = resolve_name(self._dt_test.name) 366 367 if isproperty(obj): 368 # properties have no connection to the class they are in 369 # so we can't just look 'em up, we have to first look up 370 # the class, then stick the prop on the end 371 parts = self._dt_test.name.split('.') 372 class_name = '.'.join(parts[:-1]) 373 cls = resolve_name(class_name) 374 base_addr = test_address(cls) 375 return (base_addr[0], base_addr[1], 376 '.'.join([base_addr[2], parts[-1]])) 377 else: 378 return test_address(obj) 379 380 # doctests loaded via find(obj) omit the module name 381 # so we need to override id, __repr__ and shortDescription 382 # bonus: this will squash a 2.3 vs 2.4 incompatiblity 383 def id(self): 384 name = self._dt_test.name 385 filename = self._dt_test.filename 386 if filename is not None: 387 pk = getpackage(filename) 388 if pk is None: 389 return name 390 if not name.startswith(pk): 391 name = "%s.%s" % (pk, name) 392 return name 393 394 def __repr__(self): 395 name = self.id() 396 name = name.split('.') 397 return "%s (%s)" % (name[-1], '.'.join(name[:-1])) 398 __str__ = __repr__ 399 400 def shortDescription(self): 401 return 'Doctest: %s' % self.id() 402 403 def setUp(self): 404 if self._result_var is not None: 405 self._old_displayhook = sys.displayhook 406 sys.displayhook = self._displayhook 407 super(DocTestCase, self).setUp() 408 409 def _displayhook(self, value): 410 if value is None: 411 return 412 setattr(builtin_mod, self._result_var, value) 413 print(repr(value)) 414 415 def tearDown(self): 416 super(DocTestCase, self).tearDown() 417 if self._result_var is not None: 418 sys.displayhook = self._old_displayhook 419 delattr(builtin_mod, self._result_var) 420 421 422class DocFileCase(doctest.DocFileCase): 423 """Overrides to provide address() method that returns the correct 424 address for the doc file case. 425 """ 426 def __init__(self, test, optionflags=0, setUp=None, tearDown=None, 427 checker=None, result_var='_'): 428 self._result_var = result_var 429 super(DocFileCase, self).__init__( 430 test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, 431 checker=None) 432 433 def address(self): 434 return (self._dt_test.filename, None, None) 435 436 def setUp(self): 437 if self._result_var is not None: 438 self._old_displayhook = sys.displayhook 439 sys.displayhook = self._displayhook 440 super(DocFileCase, self).setUp() 441 442 def _displayhook(self, value): 443 if value is None: 444 return 445 setattr(builtin_mod, self._result_var, value) 446 print(repr(value)) 447 448 def tearDown(self): 449 super(DocFileCase, self).tearDown() 450 if self._result_var is not None: 451 sys.displayhook = self._old_displayhook 452 delattr(builtin_mod, self._result_var) 453