1""" 2Test Loader 3----------- 4 5nose's test loader implements the same basic functionality as its 6superclass, unittest.TestLoader, but extends it by more liberal 7interpretations of what may be a test and how a test may be named. 8""" 9 10 11import logging 12import os 13import sys 14import unittest 15import types 16from inspect import isfunction 17from nose.pyversion import unbound_method, ismethod 18from nose.case import FunctionTestCase, MethodTestCase 19from nose.failure import Failure 20from nose.config import Config 21from nose.importer import Importer, add_path, remove_path 22from nose.selector import defaultSelector, TestAddress 23from nose.util import func_lineno, getpackage, isclass, isgenerator, \ 24 ispackage, regex_last_key, resolve_name, transplant_func, \ 25 transplant_class, test_address 26from nose.suite import ContextSuiteFactory, ContextList, LazySuite 27from nose.pyversion import sort_list, cmp_to_key 28import collections 29 30 31log = logging.getLogger(__name__) 32#log.setLevel(logging.DEBUG) 33 34# for efficiency and easier mocking 35op_normpath = os.path.normpath 36op_abspath = os.path.abspath 37op_join = os.path.join 38op_isdir = os.path.isdir 39op_isfile = os.path.isfile 40 41 42__all__ = ['TestLoader', 'defaultTestLoader'] 43 44 45class TestLoader(unittest.TestLoader): 46 """Test loader that extends unittest.TestLoader to: 47 48 * Load tests from test-like functions and classes that are not 49 unittest.TestCase subclasses 50 * Find and load test modules in a directory 51 * Support tests that are generators 52 * Support easy extensions of or changes to that behavior through plugins 53 """ 54 config = None 55 importer = None 56 workingDir = None 57 selector = None 58 suiteClass = None 59 60 def __init__(self, config=None, importer=None, workingDir=None, 61 selector=None): 62 """Initialize a test loader. 63 64 Parameters (all optional): 65 66 * config: provide a `nose.config.Config`_ or other config class 67 instance; if not provided a `nose.config.Config`_ with 68 default values is used. 69 * importer: provide an importer instance that implements 70 `importFromPath`. If not provided, a 71 `nose.importer.Importer`_ is used. 72 * workingDir: the directory to which file and module names are 73 relative. If not provided, assumed to be the current working 74 directory. 75 * selector: a selector class or instance. If a class is 76 provided, it will be instantiated with one argument, the 77 current config. If not provided, a `nose.selector.Selector`_ 78 is used. 79 """ 80 if config is None: 81 config = Config() 82 if importer is None: 83 importer = Importer(config=config) 84 if workingDir is None: 85 workingDir = config.workingDir 86 if selector is None: 87 selector = defaultSelector(config) 88 elif isclass(selector): 89 selector = selector(config) 90 self.config = config 91 self.importer = importer 92 self.workingDir = op_normpath(op_abspath(workingDir)) 93 self.selector = selector 94 if config.addPaths: 95 add_path(workingDir, config) 96 self.suiteClass = ContextSuiteFactory(config=config) 97 98 self._visitedPaths = set([]) 99 100 unittest.TestLoader.__init__(self) 101 102 def getTestCaseNames(self, testCaseClass): 103 """Override to select with selector, unless 104 config.getTestCaseNamesCompat is True 105 """ 106 if self.config.getTestCaseNamesCompat: 107 return unittest.TestLoader.getTestCaseNames(self, testCaseClass) 108 109 def wanted(attr, cls=testCaseClass, sel=self.selector): 110 item = getattr(cls, attr, None) 111 if isfunction(item): 112 item = unbound_method(cls, item) 113 elif not ismethod(item): 114 return False 115 return sel.wantMethod(item) 116 117 cases = list(filter(wanted, dir(testCaseClass))) 118 119 # add runTest if nothing else picked 120 if not cases and hasattr(testCaseClass, 'runTest'): 121 cases = ['runTest'] 122 if self.sortTestMethodsUsing: 123 sort_list(cases, cmp_to_key(self.sortTestMethodsUsing)) 124 return cases 125 126 def _haveVisited(self, path): 127 # For cases where path is None, we always pretend we haven't visited 128 # them. 129 if path is None: 130 return False 131 132 return path in self._visitedPaths 133 134 def _addVisitedPath(self, path): 135 if path is not None: 136 self._visitedPaths.add(path) 137 138 def loadTestsFromDir(self, path): 139 """Load tests from the directory at path. This is a generator 140 -- each suite of tests from a module or other file is yielded 141 and is expected to be executed before the next file is 142 examined. 143 """ 144 log.debug("load from dir %s", path) 145 plugins = self.config.plugins 146 plugins.beforeDirectory(path) 147 if self.config.addPaths: 148 paths_added = add_path(path, self.config) 149 150 entries = os.listdir(path) 151 sort_list(entries, regex_last_key(self.config.testMatch)) 152 for entry in entries: 153 # this hard-coded initial-dot test will be removed: 154 # http://code.google.com/p/python-nose/issues/detail?id=82 155 if entry.startswith('.'): 156 continue 157 entry_path = op_abspath(op_join(path, entry)) 158 is_file = op_isfile(entry_path) 159 wanted = False 160 if is_file: 161 is_dir = False 162 wanted = self.selector.wantFile(entry_path) 163 else: 164 is_dir = op_isdir(entry_path) 165 if is_dir: 166 # this hard-coded initial-underscore test will be removed: 167 # http://code.google.com/p/python-nose/issues/detail?id=82 168 if entry.startswith('_'): 169 continue 170 wanted = self.selector.wantDirectory(entry_path) 171 is_package = ispackage(entry_path) 172 173 # Python 3.3 now implements PEP 420: Implicit Namespace Packages. 174 # As a result, it's now possible that parent paths that have a 175 # segment with the same basename as our package ends up 176 # in module.__path__. So we have to keep track of what we've 177 # visited, and not-revisit them again. 178 if wanted and not self._haveVisited(entry_path): 179 self._addVisitedPath(entry_path) 180 if is_file: 181 plugins.beforeContext() 182 if entry.endswith('.py'): 183 yield self.loadTestsFromName( 184 entry_path, discovered=True) 185 else: 186 yield self.loadTestsFromFile(entry_path) 187 plugins.afterContext() 188 elif is_package: 189 # Load the entry as a package: given the full path, 190 # loadTestsFromName() will figure it out 191 yield self.loadTestsFromName( 192 entry_path, discovered=True) 193 else: 194 # Another test dir in this one: recurse lazily 195 yield self.suiteClass( 196 lambda: self.loadTestsFromDir(entry_path)) 197 tests = [] 198 for test in plugins.loadTestsFromDir(path): 199 tests.append(test) 200 # TODO: is this try/except needed? 201 try: 202 if tests: 203 yield self.suiteClass(tests) 204 except (KeyboardInterrupt, SystemExit): 205 raise 206 except: 207 yield self.suiteClass([Failure(*sys.exc_info())]) 208 209 # pop paths 210 if self.config.addPaths: 211 for p in paths_added: 212 remove_path(p) 213 plugins.afterDirectory(path) 214 215 def loadTestsFromFile(self, filename): 216 """Load tests from a non-module file. Default is to raise a 217 ValueError; plugins may implement `loadTestsFromFile` to 218 provide a list of tests loaded from the file. 219 """ 220 log.debug("Load from non-module file %s", filename) 221 try: 222 tests = [test for test in 223 self.config.plugins.loadTestsFromFile(filename)] 224 if tests: 225 # Plugins can yield False to indicate that they were 226 # unable to load tests from a file, but it was not an 227 # error -- the file just had no tests to load. 228 tests = [_f for _f in tests if _f] 229 return self.suiteClass(tests) 230 else: 231 # Nothing was able to even try to load from this file 232 open(filename, 'r').close() # trigger os error 233 raise ValueError("Unable to load tests from file %s" 234 % filename) 235 except (KeyboardInterrupt, SystemExit): 236 raise 237 except: 238 exc = sys.exc_info() 239 return self.suiteClass( 240 [Failure(exc[0], exc[1], exc[2], 241 address=(filename, None, None))]) 242 243 def loadTestsFromGenerator(self, generator, module): 244 """Lazy-load tests from a generator function. The generator function 245 may yield either: 246 247 * a callable, or 248 * a function name resolvable within the same module 249 """ 250 def generate(g=generator, m=module): 251 try: 252 for test in g(): 253 test_func, arg = self.parseGeneratedTest(test) 254 if not isinstance(test_func, collections.Callable): 255 test_func = getattr(m, test_func) 256 yield FunctionTestCase(test_func, arg=arg, descriptor=g) 257 except KeyboardInterrupt: 258 raise 259 except: 260 exc = sys.exc_info() 261 yield Failure(exc[0], exc[1], exc[2], 262 address=test_address(generator)) 263 return self.suiteClass(generate, context=generator, can_split=False) 264 265 def loadTestsFromGeneratorMethod(self, generator, cls): 266 """Lazy-load tests from a generator method. 267 268 This is more complicated than loading from a generator function, 269 since a generator method may yield: 270 271 * a function 272 * a bound or unbound method, or 273 * a method name 274 """ 275 # convert the unbound generator method 276 # into a bound method so it can be called below 277 if hasattr(generator, 'im_class'): 278 cls = generator.__self__.__class__ 279 inst = cls() 280 method = generator.__name__ 281 generator = getattr(inst, method) 282 283 def generate(g=generator, c=cls): 284 try: 285 for test in g(): 286 test_func, arg = self.parseGeneratedTest(test) 287 if not isinstance(test_func, collections.Callable): 288 test_func = unbound_method(c, getattr(c, test_func)) 289 if ismethod(test_func): 290 yield MethodTestCase(test_func, arg=arg, descriptor=g) 291 elif isinstance(test_func, collections.Callable): 292 # In this case we're forcing the 'MethodTestCase' 293 # to run the inline function as its test call, 294 # but using the generator method as the 'method of 295 # record' (so no need to pass it as the descriptor) 296 yield MethodTestCase(g, test=test_func, arg=arg) 297 else: 298 yield Failure( 299 TypeError, 300 "%s is not a callable or method" % test_func) 301 except KeyboardInterrupt: 302 raise 303 except: 304 exc = sys.exc_info() 305 yield Failure(exc[0], exc[1], exc[2], 306 address=test_address(generator)) 307 return self.suiteClass(generate, context=generator, can_split=False) 308 309 def loadTestsFromModule(self, module, path=None, discovered=False): 310 """Load all tests from module and return a suite containing 311 them. If the module has been discovered and is not test-like, 312 the suite will be empty by default, though plugins may add 313 their own tests. 314 """ 315 log.debug("Load from module %s", module) 316 tests = [] 317 test_classes = [] 318 test_funcs = [] 319 # For *discovered* modules, we only load tests when the module looks 320 # testlike. For modules we've been directed to load, we always 321 # look for tests. (discovered is set to True by loadTestsFromDir) 322 if not discovered or self.selector.wantModule(module): 323 for item in dir(module): 324 test = getattr(module, item, None) 325 # print "Check %s (%s) in %s" % (item, test, module.__name__) 326 if isclass(test): 327 if self.selector.wantClass(test): 328 test_classes.append(test) 329 elif isfunction(test) and self.selector.wantFunction(test): 330 test_funcs.append(test) 331 sort_list(test_classes, lambda x: x.__name__) 332 sort_list(test_funcs, func_lineno) 333 tests = [self.makeTest(t, parent=module) for t in test_classes + test_funcs] 334 335 # Now, descend into packages 336 # FIXME can or should this be lazy? 337 # is this syntax 2.2 compatible? 338 module_paths = getattr(module, '__path__', []) 339 340 if path: 341 path = os.path.normcase(os.path.realpath(path)) 342 343 for module_path in module_paths: 344 log.debug("Load tests from module path %s?", module_path) 345 log.debug("path: %s os.path.realpath(%s): %s", 346 path, os.path.normcase(module_path), 347 os.path.realpath(os.path.normcase(module_path))) 348 if (self.config.traverseNamespace or not path) or \ 349 os.path.realpath( 350 os.path.normcase(module_path)).startswith(path): 351 # Egg files can be on sys.path, so make sure the path is a 352 # directory before trying to load from it. 353 if os.path.isdir(module_path): 354 tests.extend(self.loadTestsFromDir(module_path)) 355 356 for test in self.config.plugins.loadTestsFromModule(module, path): 357 tests.append(test) 358 359 return self.suiteClass(ContextList(tests, context=module)) 360 361 def loadTestsFromName(self, name, module=None, discovered=False): 362 """Load tests from the entity with the given name. 363 364 The name may indicate a file, directory, module, or any object 365 within a module. See `nose.util.split_test_name` for details on 366 test name parsing. 367 """ 368 # FIXME refactor this method into little bites? 369 log.debug("load from %s (%s)", name, module) 370 371 suite = self.suiteClass 372 373 # give plugins first crack 374 plug_tests = self.config.plugins.loadTestsFromName(name, module) 375 if plug_tests: 376 return suite(plug_tests) 377 378 addr = TestAddress(name, workingDir=self.workingDir) 379 if module: 380 # Two cases: 381 # name is class.foo 382 # The addr will be incorrect, since it thinks class.foo is 383 # a dotted module name. It's actually a dotted attribute 384 # name. In this case we want to use the full submitted 385 # name as the name to load from the module. 386 # name is module:class.foo 387 # The addr will be correct. The part we want is the part after 388 # the :, which is in addr.call. 389 if addr.call: 390 name = addr.call 391 parent, obj = self.resolve(name, module) 392 if (isclass(parent) 393 and getattr(parent, '__module__', None) != module.__name__ 394 and not isinstance(obj, Failure)): 395 parent = transplant_class(parent, module.__name__) 396 obj = getattr(parent, obj.__name__) 397 log.debug("parent %s obj %s module %s", parent, obj, module) 398 if isinstance(obj, Failure): 399 return suite([obj]) 400 else: 401 return suite(ContextList([self.makeTest(obj, parent)], 402 context=parent)) 403 else: 404 if addr.module: 405 try: 406 if addr.filename is None: 407 module = resolve_name(addr.module) 408 else: 409 self.config.plugins.beforeImport( 410 addr.filename, addr.module) 411 # FIXME: to support module.name names, 412 # do what resolve-name does and keep trying to 413 # import, popping tail of module into addr.call, 414 # until we either get an import or run out of 415 # module parts 416 try: 417 module = self.importer.importFromPath( 418 addr.filename, addr.module) 419 finally: 420 self.config.plugins.afterImport( 421 addr.filename, addr.module) 422 except (KeyboardInterrupt, SystemExit): 423 raise 424 except: 425 exc = sys.exc_info() 426 return suite([Failure(exc[0], exc[1], exc[2], 427 address=addr.totuple())]) 428 if addr.call: 429 return self.loadTestsFromName(addr.call, module) 430 else: 431 return self.loadTestsFromModule( 432 module, addr.filename, 433 discovered=discovered) 434 elif addr.filename: 435 path = addr.filename 436 if addr.call: 437 package = getpackage(path) 438 if package is None: 439 return suite([ 440 Failure(ValueError, 441 "Can't find callable %s in file %s: " 442 "file is not a python module" % 443 (addr.call, path), 444 address=addr.totuple())]) 445 return self.loadTestsFromName(addr.call, module=package) 446 else: 447 if op_isdir(path): 448 # In this case we *can* be lazy since we know 449 # that each module in the dir will be fully 450 # loaded before its tests are executed; we 451 # also know that we're not going to be asked 452 # to load from . and ./some_module.py *as part 453 # of this named test load* 454 return LazySuite( 455 lambda: self.loadTestsFromDir(path)) 456 elif op_isfile(path): 457 return self.loadTestsFromFile(path) 458 else: 459 return suite([ 460 Failure(OSError, "No such file %s" % path, 461 address=addr.totuple())]) 462 else: 463 # just a function? what to do? I think it can only be 464 # handled when module is not None 465 return suite([ 466 Failure(ValueError, "Unresolvable test name %s" % name, 467 address=addr.totuple())]) 468 469 def loadTestsFromNames(self, names, module=None): 470 """Load tests from all names, returning a suite containing all 471 tests. 472 """ 473 plug_res = self.config.plugins.loadTestsFromNames(names, module) 474 if plug_res: 475 suite, names = plug_res 476 if suite: 477 return self.suiteClass([ 478 self.suiteClass(suite), 479 unittest.TestLoader.loadTestsFromNames(self, names, module) 480 ]) 481 return unittest.TestLoader.loadTestsFromNames(self, names, module) 482 483 def loadTestsFromTestCase(self, testCaseClass): 484 """Load tests from a unittest.TestCase subclass. 485 """ 486 cases = [] 487 plugins = self.config.plugins 488 for case in plugins.loadTestsFromTestCase(testCaseClass): 489 cases.append(case) 490 # For efficiency in the most common case, just call and return from 491 # super. This avoids having to extract cases and rebuild a context 492 # suite when there are no plugin-contributed cases. 493 if not cases: 494 return super(TestLoader, self).loadTestsFromTestCase(testCaseClass) 495 cases.extend( 496 [case for case in 497 super(TestLoader, self).loadTestsFromTestCase(testCaseClass)]) 498 return self.suiteClass(cases) 499 500 def loadTestsFromTestClass(self, cls): 501 """Load tests from a test class that is *not* a unittest.TestCase 502 subclass. 503 504 In this case, we can't depend on the class's `__init__` taking method 505 name arguments, so we have to compose a MethodTestCase for each 506 method in the class that looks testlike. 507 """ 508 def wanted(attr, cls=cls, sel=self.selector): 509 item = getattr(cls, attr, None) 510 if isfunction(item): 511 item = unbound_method(cls, item) 512 elif not ismethod(item): 513 return False 514 return sel.wantMethod(item) 515 cases = [self.makeTest(getattr(cls, case), cls) 516 for case in filter(wanted, dir(cls))] 517 for test in self.config.plugins.loadTestsFromTestClass(cls): 518 cases.append(test) 519 return self.suiteClass(ContextList(cases, context=cls)) 520 521 def makeTest(self, obj, parent=None): 522 try: 523 return self._makeTest(obj, parent) 524 except (KeyboardInterrupt, SystemExit): 525 raise 526 except: 527 exc = sys.exc_info() 528 try: 529 addr = test_address(obj) 530 except KeyboardInterrupt: 531 raise 532 except: 533 addr = None 534 return Failure(exc[0], exc[1], exc[2], address=addr) 535 536 def _makeTest(self, obj, parent=None): 537 """Given a test object and its parent, return a test case 538 or test suite. 539 """ 540 plug_tests = [] 541 try: 542 addr = test_address(obj) 543 except KeyboardInterrupt: 544 raise 545 except: 546 addr = None 547 for test in self.config.plugins.makeTest(obj, parent): 548 plug_tests.append(test) 549 # TODO: is this try/except needed? 550 try: 551 if plug_tests: 552 return self.suiteClass(plug_tests) 553 except (KeyboardInterrupt, SystemExit): 554 raise 555 except: 556 exc = sys.exc_info() 557 return Failure(exc[0], exc[1], exc[2], address=addr) 558 559 if isfunction(obj) and parent and not isinstance(parent, types.ModuleType): 560 # This is a Python 3.x 'unbound method'. Wrap it with its 561 # associated class.. 562 obj = unbound_method(parent, obj) 563 564 if isinstance(obj, unittest.TestCase): 565 return obj 566 elif isclass(obj): 567 if parent and obj.__module__ != parent.__name__: 568 obj = transplant_class(obj, parent.__name__) 569 if issubclass(obj, unittest.TestCase): 570 return self.loadTestsFromTestCase(obj) 571 else: 572 return self.loadTestsFromTestClass(obj) 573 elif ismethod(obj): 574 if parent is None: 575 parent = obj.__class__ 576 if issubclass(parent, unittest.TestCase): 577 return parent(obj.__name__) 578 else: 579 if isgenerator(obj): 580 return self.loadTestsFromGeneratorMethod(obj, parent) 581 else: 582 return MethodTestCase(obj) 583 elif isfunction(obj): 584 if parent and obj.__module__ != parent.__name__: 585 obj = transplant_func(obj, parent.__name__) 586 if isgenerator(obj): 587 return self.loadTestsFromGenerator(obj, parent) 588 else: 589 return FunctionTestCase(obj) 590 else: 591 return Failure(TypeError, 592 "Can't make a test from %s" % obj, 593 address=addr) 594 595 def resolve(self, name, module): 596 """Resolve name within module 597 """ 598 obj = module 599 parts = name.split('.') 600 for part in parts: 601 parent, obj = obj, getattr(obj, part, None) 602 if obj is None: 603 # no such test 604 obj = Failure(ValueError, "No such test %s" % name) 605 return parent, obj 606 607 def parseGeneratedTest(self, test): 608 """Given the yield value of a test generator, return a func and args. 609 610 This is used in the two loadTestsFromGenerator* methods. 611 612 """ 613 if not isinstance(test, tuple): # yield test 614 test_func, arg = (test, tuple()) 615 elif len(test) == 1: # yield (test,) 616 test_func, arg = (test[0], tuple()) 617 else: # yield test, foo, bar, ... 618 assert len(test) > 1 # sanity check 619 test_func, arg = (test[0], test[1:]) 620 return test_func, arg 621 622defaultTestLoader = TestLoader 623 624