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"""
9from __future__ import generators
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
28
29
30log = logging.getLogger(__name__)
31#log.setLevel(logging.DEBUG)
32
33# for efficiency and easier mocking
34op_normpath = os.path.normpath
35op_abspath = os.path.abspath
36op_join = os.path.join
37op_isdir = os.path.isdir
38op_isfile = os.path.isfile
39
40
41__all__ = ['TestLoader', 'defaultTestLoader']
42
43
44class TestLoader(unittest.TestLoader):
45    """Test loader that extends unittest.TestLoader to:
46
47    * Load tests from test-like functions and classes that are not
48      unittest.TestCase subclasses
49    * Find and load test modules in a directory
50    * Support tests that are generators
51    * Support easy extensions of or changes to that behavior through plugins
52    """
53    config = None
54    importer = None
55    workingDir = None
56    selector = None
57    suiteClass = None
58
59    def __init__(self, config=None, importer=None, workingDir=None,
60                 selector=None):
61        """Initialize a test loader.
62
63        Parameters (all optional):
64
65        * config: provide a `nose.config.Config`_ or other config class
66          instance; if not provided a `nose.config.Config`_ with
67          default values is used.
68        * importer: provide an importer instance that implements
69          `importFromPath`. If not provided, a
70          `nose.importer.Importer`_ is used.
71        * workingDir: the directory to which file and module names are
72          relative. If not provided, assumed to be the current working
73          directory.
74        * selector: a selector class or instance. If a class is
75          provided, it will be instantiated with one argument, the
76          current config. If not provided, a `nose.selector.Selector`_
77          is used.
78        """
79        if config is None:
80            config = Config()
81        if importer is None:
82            importer = Importer(config=config)
83        if workingDir is None:
84            workingDir = config.workingDir
85        if selector is None:
86            selector = defaultSelector(config)
87        elif isclass(selector):
88            selector = selector(config)
89        self.config = config
90        self.importer = importer
91        self.workingDir = op_normpath(op_abspath(workingDir))
92        self.selector = selector
93        if config.addPaths:
94            add_path(workingDir, config)
95        self.suiteClass = ContextSuiteFactory(config=config)
96
97        self._visitedPaths = set([])
98
99        unittest.TestLoader.__init__(self)
100
101    def getTestCaseNames(self, testCaseClass):
102        """Override to select with selector, unless
103        config.getTestCaseNamesCompat is True
104        """
105        if self.config.getTestCaseNamesCompat:
106            return unittest.TestLoader.getTestCaseNames(self, testCaseClass)
107
108        def wanted(attr, cls=testCaseClass, sel=self.selector):
109            item = getattr(cls, attr, None)
110            if isfunction(item):
111                item = unbound_method(cls, item)
112            elif not ismethod(item):
113                return False
114            return sel.wantMethod(item)
115
116        cases = filter(wanted, dir(testCaseClass))
117
118        # add runTest if nothing else picked
119        if not cases and hasattr(testCaseClass, 'runTest'):
120            cases = ['runTest']
121        if self.sortTestMethodsUsing:
122            sort_list(cases, cmp_to_key(self.sortTestMethodsUsing))
123        return cases
124
125    def _haveVisited(self, path):
126        # For cases where path is None, we always pretend we haven't visited
127        # them.
128        if path is None:
129            return False
130
131        return path in self._visitedPaths
132
133    def _addVisitedPath(self, path):
134        if path is not None:
135            self._visitedPaths.add(path)
136
137    def loadTestsFromDir(self, path):
138        """Load tests from the directory at path. This is a generator
139        -- each suite of tests from a module or other file is yielded
140        and is expected to be executed before the next file is
141        examined.
142        """
143        log.debug("load from dir %s", path)
144        plugins = self.config.plugins
145        plugins.beforeDirectory(path)
146        if self.config.addPaths:
147            paths_added = add_path(path, self.config)
148
149        entries = os.listdir(path)
150        sort_list(entries, regex_last_key(self.config.testMatch))
151        for entry in entries:
152            # this hard-coded initial-dot test will be removed:
153            # http://code.google.com/p/python-nose/issues/detail?id=82
154            if entry.startswith('.'):
155                continue
156            entry_path = op_abspath(op_join(path, entry))
157            is_file = op_isfile(entry_path)
158            wanted = False
159            if is_file:
160                is_dir = False
161                wanted = self.selector.wantFile(entry_path)
162            else:
163                is_dir = op_isdir(entry_path)
164                if is_dir:
165                    # this hard-coded initial-underscore test will be removed:
166                    # http://code.google.com/p/python-nose/issues/detail?id=82
167                    if entry.startswith('_'):
168                        continue
169                    wanted = self.selector.wantDirectory(entry_path)
170            is_package = ispackage(entry_path)
171
172            # Python 3.3 now implements PEP 420: Implicit Namespace Packages.
173            # As a result, it's now possible that parent paths that have a
174            # segment with the same basename as our package ends up
175            # in module.__path__.  So we have to keep track of what we've
176            # visited, and not-revisit them again.
177            if wanted and not self._haveVisited(entry_path):
178                self._addVisitedPath(entry_path)
179                if is_file:
180                    plugins.beforeContext()
181                    if entry.endswith('.py'):
182                        yield self.loadTestsFromName(
183                            entry_path, discovered=True)
184                    else:
185                        yield self.loadTestsFromFile(entry_path)
186                    plugins.afterContext()
187                elif is_package:
188                    # Load the entry as a package: given the full path,
189                    # loadTestsFromName() will figure it out
190                    yield self.loadTestsFromName(
191                        entry_path, discovered=True)
192                else:
193                    # Another test dir in this one: recurse lazily
194                    yield self.suiteClass(
195                        lambda: self.loadTestsFromDir(entry_path))
196        tests = []
197        for test in plugins.loadTestsFromDir(path):
198            tests.append(test)
199        # TODO: is this try/except needed?
200        try:
201            if tests:
202                yield self.suiteClass(tests)
203        except (KeyboardInterrupt, SystemExit):
204            raise
205        except:
206            yield self.suiteClass([Failure(*sys.exc_info())])
207
208        # pop paths
209        if self.config.addPaths:
210            for p in paths_added:
211              remove_path(p)
212        plugins.afterDirectory(path)
213
214    def loadTestsFromFile(self, filename):
215        """Load tests from a non-module file. Default is to raise a
216        ValueError; plugins may implement `loadTestsFromFile` to
217        provide a list of tests loaded from the file.
218        """
219        log.debug("Load from non-module file %s", filename)
220        try:
221            tests = [test for test in
222                     self.config.plugins.loadTestsFromFile(filename)]
223            if tests:
224                # Plugins can yield False to indicate that they were
225                # unable to load tests from a file, but it was not an
226                # error -- the file just had no tests to load.
227                tests = filter(None, tests)
228                return self.suiteClass(tests)
229            else:
230                # Nothing was able to even try to load from this file
231                open(filename, 'r').close() # trigger os error
232                raise ValueError("Unable to load tests from file %s"
233                                 % filename)
234        except (KeyboardInterrupt, SystemExit):
235            raise
236        except:
237            exc = sys.exc_info()
238            return self.suiteClass(
239                [Failure(exc[0], exc[1], exc[2],
240                         address=(filename, None, None))])
241
242    def loadTestsFromGenerator(self, generator, module):
243        """Lazy-load tests from a generator function. The generator function
244        may yield either:
245
246        * a callable, or
247        * a function name resolvable within the same module
248        """
249        def generate(g=generator, m=module):
250            try:
251                for test in g():
252                    test_func, arg = self.parseGeneratedTest(test)
253                    if not callable(test_func):
254                        test_func = getattr(m, test_func)
255                    yield FunctionTestCase(test_func, arg=arg, descriptor=g)
256            except KeyboardInterrupt:
257                raise
258            except:
259                exc = sys.exc_info()
260                yield Failure(exc[0], exc[1], exc[2],
261                              address=test_address(generator))
262        return self.suiteClass(generate, context=generator, can_split=False)
263
264    def loadTestsFromGeneratorMethod(self, generator, cls):
265        """Lazy-load tests from a generator method.
266
267        This is more complicated than loading from a generator function,
268        since a generator method may yield:
269
270        * a function
271        * a bound or unbound method, or
272        * a method name
273        """
274        # convert the unbound generator method
275        # into a bound method so it can be called below
276        if hasattr(generator, 'im_class'):
277            cls = generator.im_class
278        inst = cls()
279        method = generator.__name__
280        generator = getattr(inst, method)
281
282        def generate(g=generator, c=cls):
283            try:
284                for test in g():
285                    test_func, arg = self.parseGeneratedTest(test)
286                    if not callable(test_func):
287                        test_func = unbound_method(c, getattr(c, test_func))
288                    if ismethod(test_func):
289                        yield MethodTestCase(test_func, arg=arg, descriptor=g)
290                    elif callable(test_func):
291                        # In this case we're forcing the 'MethodTestCase'
292                        # to run the inline function as its test call,
293                        # but using the generator method as the 'method of
294                        # record' (so no need to pass it as the descriptor)
295                        yield MethodTestCase(g, test=test_func, arg=arg)
296                    else:
297                        yield Failure(
298                            TypeError,
299                            "%s is not a callable or method" % test_func)
300            except KeyboardInterrupt:
301                raise
302            except:
303                exc = sys.exc_info()
304                yield Failure(exc[0], exc[1], exc[2],
305                              address=test_address(generator))
306        return self.suiteClass(generate, context=generator, can_split=False)
307
308    def loadTestsFromModule(self, module, path=None, discovered=False):
309        """Load all tests from module and return a suite containing
310        them. If the module has been discovered and is not test-like,
311        the suite will be empty by default, though plugins may add
312        their own tests.
313        """
314        log.debug("Load from module %s", module)
315        tests = []
316        test_classes = []
317        test_funcs = []
318        # For *discovered* modules, we only load tests when the module looks
319        # testlike. For modules we've been directed to load, we always
320        # look for tests. (discovered is set to True by loadTestsFromDir)
321        if not discovered or self.selector.wantModule(module):
322            for item in dir(module):
323                test = getattr(module, item, None)
324                # print "Check %s (%s) in %s" % (item, test, module.__name__)
325                if isclass(test):
326                    if self.selector.wantClass(test):
327                        test_classes.append(test)
328                elif isfunction(test) and self.selector.wantFunction(test):
329                    test_funcs.append(test)
330            sort_list(test_classes, lambda x: x.__name__)
331            sort_list(test_funcs, func_lineno)
332            tests = map(lambda t: self.makeTest(t, parent=module),
333                        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