Changeset 18

Show
Ignore:
Timestamp:
Sun Apr 2 22:26:13 2006
Author:
jpellerin
Message:
  • Add missed-test plugin (closes #8)
  • Account for base class test methods in test classes (closes #22)
  • Fix bugs and improve unit tests for loader and selector


Files:

Legend:

Unmodified
Added
Removed
Modified
  • trunk/nose/core.py

    r15 r18  
    144 144  
    145 145     # FIXME is this the same signature as unittest?  
      146     # FIXME no it is not. fix this!  
    146 147     def __init__(self, argv=None, env=None, testRunner=None,  
    147 148                  testCollector=defaultTestCollector, stream=sys.stderr):  
     
    316 317  
    317 318 def configure_logging(options):  
    318       loggers = [ '', 'nose', 'nose.core', 'nose.importer', 'nose.inspector',  
    319                   'nose.loader', 'nose.plugins', 'nose.result',  
      319     loggers = [ '', 'nose', 'nose.case', 'nose.core', 'nose.importer',  
      320                 'nose.inspector', 'nose.loader', 'nose.plugins', 'nose.result',  
    320 321                 'nose.selector', 'nose.suite' ]  
      322  
      323     # output  
      324     if options.debug_log:  
      325         format = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')  
      326         handler = logging.FileHandler(options.debug_log)  
      327         handler.setFormatter(format)  
      328         logging.getLogger('').addHandler(handler)  
      329     else:  
      330         logging.basicConfig()  
      331  
    321 332     lvl = logging.WARNING  
    322 333     if options.verbosity >= 5:  
     
    329 340         l = logging.getLogger(logger)  
    330 341         l.setLevel(lvl)  
    331    
      342          
    331 342     # individual overrides  
    332 343     if options.debug:  
     
    337 348             l.setLevel(logging.DEBUG)  
    338 349  
    339       # output  
    340       if options.debug_log:  
    341           format = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')  
    342           handler = logging.FileHandler(options.debug_log)  
    343           handler.setFormatter(format)  
    344           logging.getLogger('').addHandler(handler)  
    345       else:  
    346           logging.basicConfig()  
    347    
    348    
      350              
    349 351 def main(*arg, **kw):  
    350 352     """Collect and run test, returning success or failure  
  • trunk/nose/suite.py

    r15 r18  
    10 10 class LazySuite(unittest.TestSuite):  
    11 11  
    12       # _exc_info_to_string needs this propery  
      12     # _exc_info_to_string needs this property  
    12 12     failureException = unittest.TestCase.failureException  
    13 13      
  • trunk/nose/plugins/base.py

    r17 r18  
    4 4  
    5 5 class Plugin(object):  
    6       """Base class for nose plugins. It's not necessary to subclass this  
      6     """Base class for nose plugins. It's not *necessary* to subclass this  
    6 6     class to create a plugin; however, all plugins must implement  
    7 7     `add_options(self, parser, env)` and `configure(self, options,  
     
    86 86  
    87 87     While it is recommended that plugins subclass  
    88       nose.plugins.base.Plugin, the only requirements for a plugin are  
      88     nose.plugins.Plugin, the only requirements for a plugin are  
    88 88     that it implement the methods `add_options(self, parser, env)` and  
    89 89     `configure(self, options, conf)`, and have the attributes  
     
    96 96  
    97 97     When plugins are called, the first plugin that implements a method  
    98       and returns a non-None value wins, and plugin processing ends.  
      98     and returns a non-None value wins, and plugin processing ends. The  
      99     only exceptions to are `loadTestsFromModule`, `loadTestsFromName`,  
      100     and `loadTestsFromPath`, which allow multiple plugins to load tests.  
    99 101  
    100 102     In general, plugin methods correspond directly to the methods of  
     
    279 281      
    280 282     def prepareTest(self, test):  
    281           """Called before a test is run. If you return a non-None value,  
      283         """Called before the test is run by the test runner. Please note  
      284         the article *the* in the previous sentence: prepareTest is  
      285         called *only once*, and is passed the test case or test suite  
      286         that the test runner will execute. It is *not* called for each  
      287         individual test case. If you return a non-None value,  
    282 288         that return value will be run as the test. Use this hook to wrap  
    283           or decorate tests with other functions.  
      289         or decorate the test with another function.  
    283 289  
    284 290         Parameters:  
     
    291 297     def report(self, stream):  
    292 298         """Called after all error output has been printed. Print your  
    293           plugin's report to the provided stream.  
      299         plugin's report to the provided stream. Return None to allow  
      300         other plugins to print reports, any other value to stop them.  
    294 301  
    295 302         Parameters:  
     
    311 318      
    312 319     def startTest(self, test):  
    313           """Called before each test is run.  
      320         """Called before each test is run. DO NOT return a value unless  
      321         you want to stop other plugins from seeing the test start.  
    314 322  
    315 323         Parameters:  
     
    320 328      
    321 329     def stopTest(self, test):  
    322           """Called after each test is run.  
      330         """Called after each test is run. DO NOT return a value unless  
      331         you want to stop other plugins from seeing that the test has stopped.  
    323 332  
    324 333         Parameters:  
  • trunk/nose/plugins/__init__.py

    r9 r18  
    7 7 ---------------  
    8 8  
    9   Plugin classes should subclass nose.plugins.Plugin or one of the other  
    10   classes in nose.plugins.base.  
      9 Plugin classes should subclass nose.plugins.Plugin.  
    11 10  
    12   Plugins may implement any of the methods in any or all of the interfaces  
    13   described in nose.plugins.base.  
      11 Plugins may implement any of the methods described in the class  
      12 PluginInterface in nose.plugins.base. Please note that this class is for  
      13 documentary purposes only; plugins may not subclass PluginInterface.  
    14 14  
    15 15 Registering  
     
    46 46 logging  
    47 47  
      48 Recipes  
      49 =======  
      50  
      51  * Writing a plugin that monitors or controls test result output  
      52  
      53    Implement any or all of addError, addFailure, etc., to monitor test  
      54    results. If you also want to monitor output, implement  
      55    setOutputStream and keep a reference to the output stream. If you  
      56    want to prevent the builtin TextTestResult output, implement  
      57    setOutputSteam and return a dummy stream and send your desired output  
      58    to the real stream.  
      59    
      60    Example: examples/html_plugin/htmlplug.py  
      61  
      62  * Writing a plugin that loads tests from files other than python modules  
      63  
      64    Implement wantFile and loadTestsFromPath. In wantFile, return True  
      65    for files that you want to examine for tests. In loadTestsFromPath,  
      66    for those files, return a TestSuite or other iterable containing  
      67    TestCases. loadTestsFromPath may also be a generator.  
      68    
      69    Example: nose.plugins.doctests  
      70  
      71  * Writing a plugin that prints a report  
      72  
      73    Implement begin if you need to perform setup before testing  
      74    begins. Implement report and output your report to the provided stream.  
      75    
      76    Examples: nose.plugins.cover, nose.plugins.profile, nose.plugins.missed  
      77  
      78  * Writing a plugin that selects or rejects tests  
      79  
      80    Implement any or all want* methods. Return False to reject the test  
      81    candidate, True to accept it -- which  means that the test candidate  
      82    will pass through the rest of the system, so you must be prepared to  
      83    load tests from it if tests can't be loaded by the core loader or  
      84    another plugin -- and None if you don't care.  
      85  
      86    Examples: nose.plugins.attrib, nose.plugins.doctests  
      87      
    48 88 Examples  
    49 89 ========  
  • trunk/nose/util.py

    r15 r18  
    6 6 import sys  
    7 7 import types  
      8 import unittest  
    8 9  
    9 10 log = logging.getLogger('nose')  
     
    101 102         return (cls_adr[0], cls_adr[1],  
    102 103                 "%s.%s" % (cls_adr[2], test.__name__))  
      104     # handle unittest.TestCase instances  
      105     if isinstance(test, unittest.TestCase):  
      106         if hasattr(test, 'testFunc'):  
      107             # nose FunctionTestCase  
      108             return test_address(test.testFunc)  
      109         if hasattr(test, '_FunctionTestCase__testFunc'):  
      110             # unittest FunctionTestCase  
      111             return test_address(test._FunctionTestCase__testFunc)  
      112         if hasattr(test, 'testCase'):  
      113             # nose MethodTestCase  
      114             return test_address(test.testCase)  
      115         # regular unittest.TestCase  
      116         cls_adr = test_address(test.__class__)  
      117         return (cls_adr[0], cls_adr[1],  
      118                 "%s.%s" % (cls_adr[2], test._TestCase__testMethodName))  
    103 119     raise TypeError("I don't know what %s is (%s)" % (test, t))  
    104 120  
  • trunk/nose/result.py

    r17 r18  
    39 39         whole to be considered non-successful.  
    40 40     """  
    41       separator3 = '.' * 70  
    42 41      
    43 42     def __init__(self, stream, descriptions, verbosity, conf):         
  • trunk/nose/selector.py

    r14 r18  
    28 28         """  
    29 29         if not self.tests:  
    30               return None  
    31           failed = False  
    32           for test in self.tests:  
    33               filename, modname, funcname = split_test_name(test)  
    34               res = func(filename, modname, funcname)  
    35               if res:  
    36                   # a match  
    37                   return True  
    38               elif res is False:  
    39                   # a direct miss  
    40                   failed = True  
    41           return not failed  
      30             log.debug("No tests to check")  
      31             return True  
      32          
      33         log.debug('tests to check: %s', self.tests)         
      34         matches = [ func(*test_tuple) for test_tuple in  
      35                     map(split_test_name, self.tests) ]  
      36         return filter(lambda x: x is not False, matches)  
    42 37  
    43 38     def callableInTests(self, clb, matches):  
     
    58 53                         containerMatch = True  
    59 54             if modname is not None and not containerMatch:  
    60                   if clb.__module__.startswith(modname):  
    61                       containerMatch = True  
      55                 try:  
      56                     log.debug("Check container match: %s v %s",  
      57                               clb.__module__, modname)  
      58                     if clb.__module__.startswith(modname):  
      59                         containerMatch = True  
      60                 except AttributeError:  
      61                     # some kind of weird attribute; this can't be a test  
      62                     return False  
    62 63             if ((filename is not None or modname is not None)  
    63 64                 and not containerMatch):  
     
    84 85         self.match = conf.testMatch         
    85 86         self.tests = conf.tests  
    86        
      87                        
    86 87     def fileInTests(self, file):  
    87 88         def match(filename, modname, funcname, file=file):  
     
    116 117     def moduleInTests(self, module, either=False):  
    117 118         def match(filename, modname, funcname, module=module, either=either):  
      119             log.debug("Checking module %s in test %s:%s (either: %s)",  
      120                       module.__name__, modname, funcname, either)  
    118 121             if modname is None:  
    119 122                 return None  
    120 123             mname = module.__name__  
    121               return (mname.startswith(modname)  
    122                       or (either and modname.startswith(mname)))  
    123           return self.anytest(match)  
    124    
      124             result = (mname.startswith(modname)  
      125                       or (either and modname.startswith(mname)))  
      126             log.debug("Module %s match %s (either: %s) result %s",  
      127                       module.__name__, modname, either, result)  
      128             return result  
      129         res = self.anytest(match)  
      130         log.debug("Module %s in tests result: %s", module.__name__, res)  
      131         return res  
    125 132      
    126 133     def wantClass(self, cls):  
     
    133 140         self.tests:  
    134 141         """  
      142         log.debug("Load tests from class %s?", cls)  
      143          
    135 144         wanted = (not cls.__name__.startswith('_')  
    136 145                   and (issubclass(cls, unittest.TestCase)  
    137 146                        or self.match.search(cls.__name__)))  
    138           plug_wants = call_plugins(self.plugins, 'wantClass', cls)  
      147         log.debug("%s is wanted? %s", cls, wanted)  
      148         plug_wants = call_plugins(self.plugins, 'wantClass', cls)         
    139 149         if plug_wants is not None:  
      150             log.debug("Plugin setting selection of %s to %s", cls, plug_wants)  
    140 151             wanted = plug_wants  
    141           return wanted and (self.classInTests(cls) is not False)  
      152         return wanted and self.classInTests(cls)  
    141 152  
    142 153     def wantDirectory(self, dirname):  
     
    175 186         files.         
    176 187         """  
      188         in_tests = self.fileInTests(file)  
      189         if not in_tests:  
      190             return False  
      191          
    177 192         base = os.path.basename(file)  
    178 193         root, ext = os.path.splitext(base)  
     
    187 202         if plug_wants is not None:  
    188 203             wanted = plug_wants  
    189           return wanted and (self.fileInTests(file) is not False)  
      204         return wanted or (pysrc and self.tests and in_tests)  
    189 204  
    190 205     def wantFunction(self, function):  
     
    201 216             # not a function  
    202 217             return False  
      218         in_tests = self.funcInTests(function)  
      219         if not in_tests:  
      220             return False         
    203 221         wanted = not funcname.startswith('_') and self.matches(funcname)  
    204 222         plug_wants = call_plugins(self.plugins, 'wantFunction', function)  
    205 223         if plug_wants is not None:  
    206 224             wanted = plug_wants  
    207           return wanted and (self.funcInTests(function) is not False)  
      225         return wanted  
    207 225  
    208 226     def wantMethod(self, method):  
     
    219 237             # not a method  
    220 238             return False  
    221           wanted = (not method_name.startswith('_')  
    222                     and self.matches(method_name))  
      239         if method_name.startswith('_'):  
      240             # never collect 'private' methods  
      241             return False  
      242         in_tests = self.methodInTests(method)  
      243         if not in_tests:  
      244             return False         
      245         wanted = self.matches(method_name)  
    223 246         plug_wants = call_plugins(self.plugins, 'wantMethod', method)  
    224 247         if plug_wants is not None:  
    225 248             wanted = plug_wants  
    226           return wanted and (self.methodInTests(method) is not False)  
      249         return wanted  
    226 249      
    227 250     def wantModule(self, module):  
     
    236 259         modules where wantModuleTests() is true.  
    237 260         """  
    238           wanted = self.matches(module.__name__.split('.')[-1])  
    239 261         in_tests = self.moduleInTests(module, either=True)  
      262         if not in_tests:  
      263             return False         
      264         wanted = self.matches(module.__name__.split('.')[-1])  
    240 265         plug_wants = call_plugins(self.plugins, 'wantModule', module)  
    241           if in_tests is False:  
    242               return False  
    243           if plug_wants is False:  
    244               return False  
    245           return wanted or in_tests or plug_wants  
      266         if plug_wants is not None:  
      267             wanted = plug_wants  
      268         return wanted or (self.tests and in_tests)  
    246 269  
    247 270     def wantModuleTests(self, module):  
     
    256 279         tests, but instead cause the standard collector to collect tests.  
    257 280         """  
    258    
      281         in_tests = self.moduleInTests(module)  
      282         if not in_tests:  
      283             return False  
      284          
    259 285         # unittest compat: always load from __main__  
    260 286         wanted = (self.matches(module.__name__.split('.')[-1])  
    261 287                   or module.__name__ == '__main__')  
    262           in_tests = self.moduleInTests(module)  
    263           plug_wants = call_plugins(self.plugins, 'wantModuleTests', module)  
    264           if in_tests is False:  
    265               return False  
    266           if plug_wants is False:  
    267               return False  
    268           return wanted or in_tests or plug_wants  
      288         plug_wants = call_plugins(self.plugins, 'wantModuleTests',  
      289                                   module)  
      290         if plug_wants is not None:  
      291             wanted = plug_wants         
      292         return wanted or (self.tests and in_tests)  
    269 293          
    270 294 defaultSelector = Selector         
  • trunk/nose/loader.py

    r17 r18  
    68 68             for test in self.testsInModule(module, importPath):  
    69 69                 tests.append(test)  
    70                        
      70  
      71         # FIXME all plugins should be allowed to play  
    71 72         # give plugins a chance  
    72 73         for plug in self.conf.plugins:  
     
    90 91                 tests.append(test)  
    91 92         # compat w/unittest  
    92           return unittest.TestSuite(tests)  
      93         return self.suiteClass(tests)  
    92 93  
    93 94     def loadTestsFromModuleName(self, module_name, package=None,  
     
    104 105         yield TestModule(self.loadTestsFromModule,  
    105 106                          self.conf, module_name, importPath)  
    106                    
      107  
      108     # FIXME s/package/module  
    107 109     def loadTestsFromName(self, name, package=None, importPath=None,  
    108 110                           module=None):  
     
    124 126         tests = None  
    125 127         path, mod_name, fn = split_test_name(name)  
    126           log.debug('test name % resolves to path %s, module %s, callable %s'  
      128         log.debug('test name %s resolves to path %s, module %s, callable %s'  
    126 128                   % (name, path, mod_name, fn))  
    127 129         if path:  
     
    160 162                 yield test  
    161 163                  
      164         # FIXME all plugins should be allowed to play                 
    162 165         res = call_plugins(self.plugins, 'loadTestsFromName',  
    163 166                            name, package, importPath)  
     
    169 172             except TypeError:  
    170 173                 yield res  
    171            
      174  
      175     # FIXME s/package/module  
      176     # if names is a string, parse it as cmd line args  
    172 177     def loadTestsFromNames(self, names, package=None, module=None):  
    173 178         # FIXME if package is set and names is not, load from package  
     
    212 217             yield TestModule(self.loadTestsFromModule,  
    213 218                              self.conf, test, importPath)  
    214                
      219  
      220         # FIXME all plugins should be allowed to play  
    215 221         # give plugins a chance  
    216 222         res = call_plugins(self.plugins, 'loadTestsFromPath',  
     
    221 227     def loadTestsFromTestCase(self, cls):  
    222 228         log.debug("collect tests in class %s", cls)  
    223           for item in dir(cls):  
    224               attr = getattr(cls, item)  
    225               wanted = False  
    226               if callable(attr) and self.selector.wantMethod(attr):  
    227                   # FIXME watch for subclasses... skip base class methods  
    228                   # unless they are overridden  
    229                   if issubclass(cls, unittest.TestCase):                     
    230                       yield cls(item)  
    231                   else:  
    232                       yield MethodTestCase(cls, item)         
      229         collected = self.testsInTestCase(cls)  
      230         if self.sortTestMethodsUsing:  
      231             collected.sort(self.sortTestMethodsUsing)  
      232          
      233         if issubclass(cls, unittest.TestCase):  
      234             maketest = cls  
      235         else:  
      236             maketest = lambda i: MethodTestCase(cls, i)  
      237         return map(maketest, collected)  
    233 238              
    234 239     def testsInModule(self, module, importPath=None):  
     
    271 276         tests.extend([ FunctionTestCase(test, fromDirectory=importPath)  
    272 277                        for test in func_tests ])  
      278         log.debug("Loaded tests %s from module %s", tests, module.__name__)  
    273 279         return tests  
    274 280  
      281     def testsInTestCase(self, cls):  
      282         collected = []  
      283         if cls in (object, type):  
      284             return collected  
      285         for item in dir(cls):  
      286             attr = getattr(cls, item)  
      287             log.debug("Check if selector wants %s (%s)", attr, cls)  
      288             if callable(attr) and self.selector.wantMethod(attr):  
      289                 collected.append(item)  
      290                  
      291         # base class methods; include those not overridden  
      292         for base in cls.__bases__:  
      293             basetests = self.testsInTestCase(base)  
      294             for test in basetests:  
      295                 if not test in collected:  
      296                     collected.append(test)  
      297         return collected  
      298  
      299     # FIXME this needs to be moved and generalized for methods  
    275 300     def generateTests(self, test):  
    276 301         cases = []  
     
    299 324         return cases  
    300 325  
      326     # FIXME this should move to util  
    301 327     def isGenerator(self, test):  
    302 328         from compiler.consts import CO_GENERATOR  
  • trunk/st/unit_tests/test_selector.py

    r14 r18  
    4 4 import nose.selector  
    5 5 from nose.config import Config  
    6   from nose.selector import Selector  
      6 from nose.selector import log, Selector  
    6 6  
    7 7 class Mod:  
     
    12 12 class TestSelector(unittest.TestCase):  
    13 13  
      14     def test_anytest(self):  
      15  
      16         def exact_file_a(filename, modname, funcname):  
      17             if filename is None:  
      18                 return None  
      19             return filename == '/a/file.py'  
      20          
      21         def exact_file_b(filename, modname, funcname):  
      22             if filename is None:  
      23                 return None  
      24             return filename == '/a/nother/file.py'  
      25  
      26         def exact_module_a(filename, modname, funcname):  
      27             if modname is None:  
      28                 return None  
      29             return modname == 'some.module'  
      30  
      31         def exact_module_b(filename, modname, funcname):  
      32             if modname is None:  
      33                 return None  
      34             return modname == 'some.other.module'  
      35  
      36         c = Config()  
      37         s = Selector(c)  
      38         s.tests = [ '/a/file.py', 'some.module' ]  
      39  
      40         # in these cases, there is a test that doesn't care  
      41         # so they all pass  
      42         assert s.anytest(exact_file_a)  
      43         assert s.anytest(exact_file_b)         
      44         assert s.anytest(exact_module_a)  
      45         assert s.anytest(exact_module_b)  
      46  
      47         # no test matches file b  
      48         s.tests = [ '/a/file.py' ]  
      49         assert s.anytest(exact_file_a)  
      50         assert not s.anytest(exact_file_b)         
      51         assert s.anytest(exact_module_a)  
      52         assert s.anytest(exact_module_b)  
      53  
      54         s.tests = [ '/a/file.py', '/that/file.py' ]  
      55         assert s.anytest(exact_file_a)  
      56         assert not s.anytest(exact_file_b)         
      57         assert s.anytest(exact_module_a)  
      58         assert s.anytest(exact_module_b)  
      59          
      60         # no test matches module b  
      61         s.tests = [ 'some.module' ]  
      62         assert s.anytest(exact_file_a)  
      63         assert s.anytest(exact_file_b)         
      64         assert s.anytest(exact_module_a)  
      65         assert not s.anytest(exact_module_b)  
      66  
      67         # no test matches module b  
      68         s.tests = [ 'some.module', 'blah.blah' ]  
      69         assert s.anytest(exact_file_a)  
      70         assert s.anytest(exact_file_b)         
      71         assert s.anytest(exact_module_a)  
      72         assert not s.anytest(exact_module_b)  
      73          
    14 74     def test_exclude(self):  
    15 75         s = Selector(Config())  
     
    22 82         assert s.matches('test_me')  
    23 83         assert not s2.matches('test_me')  
    24    
    25 84          
    26 85     def test_include(self):  
     
    34 93         assert not s.matches('meatball')  
    35 94         assert s2.matches('meatball')  
    36    
    37 95          
    38 96     def test_want_class(self):  
     
    218 276  
    219 277 if __name__ == '__main__':  
      278     # import logging  
      279     # logging.basicConfig()  
      280     # log.setLevel(logging.DEBUG)  
    220 281     unittest.main()  
  • trunk/st/unit_tests/test_utils.py

    r14 r18  
    1 1 import unittest  
    2 2 import nose  
      3 import nose.case  
    3 4  
    4 5 class TestUtils(unittest.TestCase):  
     
    36 37  
    37 38         f = Foo()  
      39  
      40         class FooTC(unittest.TestCase):  
      41             def test_one(self):  
      42                 pass