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""" 50from __future__ import generators 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 cStringIO import StringIO 63except ImportError: 64 from StringIO import StringIO 65import sys 66import __builtin__ 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, 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 filter(None, 325 [inc.search(name) 326 for inc in self.conf.include]))) 327 and (not self.conf.exclude 328 or not filter(None, 329 [exc.search(name) 330 for exc in self.conf.exclude]))) 331 332 def wantFile(self, file): 333 """Override to select all modules and any file ending with 334 configured doctest extension. 335 """ 336 # always want .py files 337 if file.endswith('.py'): 338 return True 339 # also want files that match my extension 340 if (self.extension 341 and anyp(file.endswith, self.extension) 342 and (not self.conf.exclude 343 or not filter(None, 344 [exc.search(file) 345 for exc in self.conf.exclude]))): 346 return True 347 return None 348 349 350class DocTestCase(doctest.DocTestCase): 351 """Overrides DocTestCase to 352 provide an address() method that returns the correct address for 353 the doctest case. To provide hints for address(), an obj may also 354 be passed -- this will be used as the test object for purposes of 355 determining the test address, if it is provided. 356 """ 357 def __init__(self, test, optionflags=0, setUp=None, tearDown=None, 358 checker=None, obj=None, result_var='_'): 359 self._result_var = result_var 360 self._nose_obj = obj 361 super(DocTestCase, self).__init__( 362 test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, 363 checker=checker) 364 365 def address(self): 366 if self._nose_obj is not None: 367 return test_address(self._nose_obj) 368 obj = resolve_name(self._dt_test.name) 369 370 if isproperty(obj): 371 # properties have no connection to the class they are in 372 # so we can't just look 'em up, we have to first look up 373 # the class, then stick the prop on the end 374 parts = self._dt_test.name.split('.') 375 class_name = '.'.join(parts[:-1]) 376 cls = resolve_name(class_name) 377 base_addr = test_address(cls) 378 return (base_addr[0], base_addr[1], 379 '.'.join([base_addr[2], parts[-1]])) 380 else: 381 return test_address(obj) 382 383 # doctests loaded via find(obj) omit the module name 384 # so we need to override id, __repr__ and shortDescription 385 # bonus: this will squash a 2.3 vs 2.4 incompatiblity 386 def id(self): 387 name = self._dt_test.name 388 filename = self._dt_test.filename 389 if filename is not None: 390 pk = getpackage(filename) 391 if pk is None: 392 return name 393 if not name.startswith(pk): 394 name = "%s.%s" % (pk, name) 395 return name 396 397 def __repr__(self): 398 name = self.id() 399 name = name.split('.') 400 return "%s (%s)" % (name[-1], '.'.join(name[:-1])) 401 __str__ = __repr__ 402 403 def shortDescription(self): 404 return 'Doctest: %s' % self.id() 405 406 def setUp(self): 407 if self._result_var is not None: 408 self._old_displayhook = sys.displayhook 409 sys.displayhook = self._displayhook 410 super(DocTestCase, self).setUp() 411 412 def _displayhook(self, value): 413 if value is None: 414 return 415 setattr(builtin_mod, self._result_var, value) 416 print repr(value) 417 418 def tearDown(self): 419 super(DocTestCase, self).tearDown() 420 if self._result_var is not None: 421 sys.displayhook = self._old_displayhook 422 delattr(builtin_mod, self._result_var) 423 424 425class DocFileCase(doctest.DocFileCase): 426 """Overrides to provide address() method that returns the correct 427 address for the doc file case. 428 """ 429 def __init__(self, test, optionflags=0, setUp=None, tearDown=None, 430 checker=None, result_var='_'): 431 self._result_var = result_var 432 super(DocFileCase, self).__init__( 433 test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, 434 checker=None) 435 436 def address(self): 437 return (self._dt_test.filename, None, None) 438 439 def setUp(self): 440 if self._result_var is not None: 441 self._old_displayhook = sys.displayhook 442 sys.displayhook = self._displayhook 443 super(DocFileCase, self).setUp() 444 445 def _displayhook(self, value): 446 if value is None: 447 return 448 setattr(builtin_mod, self._result_var, value) 449 print repr(value) 450 451 def tearDown(self): 452 super(DocFileCase, self).tearDown() 453 if self._result_var is not None: 454 sys.displayhook = self._old_displayhook 455 delattr(builtin_mod, self._result_var) 456