Changeset 21

Show
Ignore:
Timestamp:
Mon Apr 10 21:50:10 2006
Author:
jpellerin
Message:
  • Support test methods that are generators (#14)
  • Add docs for nose.tools (closes #31)
  • Add new, monkeypatch-free but limited setuptools integration support (#32)


Files:

Legend:

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

    r20 r21  
    10 10  
    11 11 from nose.plugins import load_plugins, call_plugins  
    12   from nose.result import install_patch, start_capture, end_capture, \  
    13       TextTestResult  
      12 from nose.result import start_capture, end_capture, TextTestResult  
    14 13 from nose.config import Config  
    15 14 from nose.loader import defaultTestLoader  
      15 from nose.proxy import ResultProxy, ResultProxySuite  
    16 16 from nose.selector import defaultSelector  
    17   from nose.suite import LazySuite, ResultProxySuite  
      17 from nose.suite import LazySuite  
    17 17 from nose.util import absdir, absfile, try_run  
    18 18  
     
    60 60     # FIXME somehow restrict plugin methods  
    61 61     conf = configure(env=os.environ)  
      62     ResultProxy.conf = conf  
    62 63     loader = defaultTestLoader(conf)  
    63 64     loader.suiteClass = ResultProxySuite  
     
    208 209         return self.success  
    209 210          
    210    
    211 211 def configure(argv=None, env=None, help=False):  
    212 212     """Configure the nose running environment. Execute configure before  
     
    317 317         if conf.where is None:  
    318 318             raise Exception("Working directory %s not found, or "  
    319                               "not a directory")  
      319                             "not a directory" % options.where)  
    319 319         log.info("Looking for tests in %s", conf.where)  
    320 320  
     
    339 339     return conf  
    340 340  
    341    
    342 341 def configure_logging(options):  
    343 342     loggers = [ '', 'nose', 'nose.case', 'nose.core', 'nose.importer',  
     
    361 360     elif options.verbosity >= 3:  
    362 361         lvl = logging.INFO  
    363       for logger in loggers:  
    364           l = logging.getLogger(logger)  
    365           l.setLevel(lvl)  
      362                  
      363     logging.getLogger('').setLevel(lvl)  
    366 364          
    367 365     # individual overrides  
     
    371 369             l = logging.getLogger(logger)  
    372 370             l.setLevel(logging.DEBUG)  
    373    
    374 371              
    375 372 def main(*arg, **kw):  
     
    385 382     """  
    386 383     return TestProgram(*arg, **kw).success  
    387        
    388            
    389   # FIXME move to tools?  
    390   def with_setup(setup=None, teardown=None):  
    391       """Decorator to add setup and/or teardown methods to a test function  
    392    
    393       @with_setup(setup, teardown)  
    394       def test_something():  
    395           # ...     
    396       """  
    397       def decorate(func, setup=setup, teardown=teardown):  
    398           if setup:  
    399               func.setup = setup  
    400           if teardown:  
    401               func.teardown = teardown  
    402           return func  
    403       return decorate  
    404    
    405    
      384              
    406 385 if __name__ == '__main__':  
    407       run_exit()  
      386     main()  
  • trunk/nose/suite.py

    r20 r21  
    2 2 import sys  
    3 3 import unittest  
      4 from nose.case import MethodTestCase  
    4 5 from nose.config import Config  
    5 6 from nose.importer import _import  
     
    8 9 log = logging.getLogger('nose.suite')  
    9 10  
      11  
    10 12 class LazySuite(unittest.TestSuite):  
    11 13  
     
    62 64  
    63 65  
    64   class ResultProxySuite(LazySuite):  
    65       pass  
    66        
      66 class GeneratorMethodTestSuite(LazySuite):  
    67 67      
      68     def __init__(self, cls, method):  
      69         self.cls = cls  
      70         self.method = method  
      71  
      72     def loadtests(self):  
      73         inst = self.cls()  
      74         suite = getattr(inst, self.method)  
      75  
      76         for test in suite():  
      77             try:  
      78                 test_method, arg = (test[0], test[1:])  
      79             except ValueError:  
      80                 test_method, arg = test[0], tuple()  
      81             log.debug('test_method: %s, arg: %s', test_method, arg)  
      82             if callable(test_method):  
      83                 name = test_method.__name__  
      84             else:  
      85                 name = test_method  
      86             yield MethodTestCase(self.cls, name, self.method, *arg)  
      87  
      88              
    68 89 class TestClass(LazySuite):  
    69 90     """Collects tests from a class.  
     
    109 130         self.module = None  
    110 131         super(TestModule, self).__init__(loadtests, conf)         
    111           self.plugins = self.conf.plugins  
    112 132          
    113 133     def __repr__(self):  
  • trunk/nose/plugins/doctests.py

    r20 r21  
    31 31     def add_options(self, parser, env=os.environ):  
    32 32         super(Doctest, self).add_options(parser, env)  
      33         parser.add_option('--doctest-tests', action='store_true',  
      34                           dest='doctest_tests',  
      35                           default=env.get('NOSE_DOCTEST_TESTS'),  
      36                           help="Also look for doctests in test modules")  
    33 37         try:  
    34 38             # 2.4 or better supports loading tests from non-modules  
     
    44 48     def configure(self, options, config):  
    45 49         super(Doctest, self).configure(options, config)  
      50         self.doctest_tests = options.doctest_tests  
    46 51         try:  
    47 52             self.extension = self.tolist(options.doctestExtension)  
     
    83 88         if name == '__init__.py':  
    84 89             return False  
    85           # FIXME don't think we need include/exclude checks here  
    86           return ((not self.conf.testMatch.search(name)  
      90         # FIXME don't think we need include/exclude checks here?  
      91         return ((self.doctest_tests or not self.conf.testMatch.search(name)  
    87 92                  or (self.conf.include is not None  
    88 93                      and self.conf.include.search(name)))  
  • trunk/nose/plugins/base.py

    r20 r21  
    240 240         pass  
    241 241      
    242       def loadTestsFromName(self, name, package=None, importPath=None):  
      242     def loadTestsFromName(self, name, module=None, importPath=None):  
    242 242         """Return tests in this file or module. Return None if you are not able  
    243 243         to load any tests, or an iterable if you are. May be a  
     
    249 249            The test name. May be a file or module name plus a test  
    250 250            callable. Use split_test_name to split into parts.  
    251            * package:  
    252              Package in which the file is found  
      251          * module:  
      252            Module in which the file is found  
    253 253          * importPath:  
    254 254            Path from which file (must be a python module) was found         
     
    256 256         pass  
    257 257  
    258       def loadTestsFromPath(self, path, package=None, importPath=None):  
      258     def loadTestsFromPath(self, path, module=None, importPath=None):  
    258 258         """Return tests in this file or directory. Return None if you are not  
    259 259         able to load any tests, or an iterable if you are. May be a  
     
    264 264          * path:  
    265 265            The full path to the file or directory.  
    266            * package:  
    267              Package in which the file/dir is found  
      266          * module:  
      267            Module in which the file/dir is found  
    268 268          * importPath:  
    269 269            Path from which file (must be a python module) was found         
  • trunk/nose/case.py

    r17 r21  
    73 73         name = "%s.%s" % (self.testFunc.__module__, name)  
    74 74  
    75           if self._seen.has_key(name):  
      75         if self._seen.has_key(name) and self.fromDirectory is not None:  
    75 75             # already seen this exact test name; put the  
    76 76             # module dir in front to disambiguate the tests  
     
    87 87 class MethodTestCase(unittest.TestCase):  
    88 88     """Test case that wraps one method in a test class.  
    89       """  
    90       def __init__(self, cls, method):  
      89     """     
      90     def __init__(self, cls, method, method_desc=None, *arg):  
    91 91         self.cls = cls  
    92 92         self.method = method  
      93         self.method_desc = method_desc  
    93 94         self.testInstance = self.cls()  
    94           self.testCase = getattr(self.testInstance, self.method)         
      95         self.testCase = getattr(self.testInstance, method)  
      96         self.arg = arg  
      97         log.debug('Test case: %s%s', self.testCase, self.arg)         
    95 98         super(MethodTestCase, self).__init__()  
    96 99          
    97 100     def __str__(self):  
    98 101         return self.id()  
    99            
      102  
      103     def desc(self):  
      104         if self.method_desc is not None:  
      105             desc = self.method_desc  
      106         else:  
      107             desc = self.method  
      108         if self.arg:  
      109             desc = "%s:%s" % (desc, self.arg)  
      110         return desc  
      111  
    100 112     def id(self):  
    101 113         return "%s.%s.%s" % (self.cls.__module__,  
    102 114                              self.cls.__name__,  
    103                                self.method)  
      115                              self.desc())  
    103 115  
    104 116     def setUp(self):  
     
    111 123  
    112 124     def runTest(self):  
    113           self.testCase()  
      125         self.testCase(*self.arg)  
    113 125          
    114 126     def tearDown(self):  
     
    128 140                                      self.testCase.__doc__)  
    129 141         return None  
      142          
      143          
  • trunk/nose/util.py

    r18 r21  
    10 10 log = logging.getLogger('nose')  
    11 11  
      12 from compiler.consts import CO_GENERATOR  
      13  
    12 14 try:  
    13 15     from cStringIO import StringIO  
     
    17 19 from nose.config import Config  
    18 20  
    19    
    20 21 def absdir(path):  
    21 22     """Return absolute, normalized path to directory, if it exists; None  
     
    29 30     return path  
    30 31  
    31    
    32 32 def absfile(path, where=None):  
    33 33     """Return absolute, normalized path to file (optionally in directory  
     
    48 48         return None  
    49 49     if os.path.isdir(path):  
    50           # might want in __init__.py from pacakge  
      50         # might want an __init__.py from pacakge  
    50 50         init = os.path.join(path,'__init__.py')  
    51 51         if os.path.isfile(init):  
     
    56 56     return None  
    57 57  
    58    
    59 58 def anyp(predicate, iterable):  
    60 59     for item in iterable:  
     
    63 62     return False  
    64 63  
    65    
    66 64 def file_like(name):  
    67 65     return os.path.dirname(name) or name.endswith('.py')  
    68 66  
      67 def is_generator(func):  
      68     try:  
      69         return func.func_code.co_flags & CO_GENERATOR != 0  
      70     except AttributeError:  
      71         return False  
    69 72  
    70 73 def split_test_name(test):  
     
    119 122     raise TypeError("I don't know what %s is (%s)" % (test, t))  
    120 123  
    121    
    122 124 def try_run(obj, names):  
    123 125     """Given a list of possible method names, try to run them with the  
     
    136 138             log.debug("call fixture %s.%s", obj, name)  
    137 139             return func()  
    138    
    139            
    140   def msg(message, min_verbosity=0, stream=None):  
    141       """Output a status message, if configured verbosity is greater than  
    142       the minimum verbosity rating of the message.  
    143       """  
    144    
    145       # FIXME ... de-globalize  
    146       if 2 > min_verbosity:  
    147           if stream is None:  
    148               stream = sys.stderr  
    149           stream.write("  " * (min_verbosity-1))  
    150           stream.write(message)  
    151           stream.write("\n")  
    152 140      
  • trunk/nose/result.py

    r18 r21  
    3 3 import sys  
    4 4 import tokenize  
    5   import unittest  
      5 from unittest import _TextTestResult  
    5 5 try:  
    6 6     from cStringIO import StringIO  
     
    12 12 from nose.exc import DeprecatedTest, SkipTest  
    13 13 from nose.plugins import call_plugins  
    14        
      14  
    14 14 buffer = StringIO()  
    15   stdout = sys.stdout  
    16   _TextTestResult = unittest._TextTestResult  
      15 stdout = []  
      16  
      17  
      18 class Result(object):  
      19     """Base class for results handlers.  
      20     """  
      21     capt = None  
      22     conf = None  
      23     tbinfo = None  
      24      
      25     def addDeprecated(self, test):  
      26         self.resetBuffer()  
      27         call_plugins(self.conf.plugins, 'addDeprecated', test)  
      28          
      29     def addError(self, test, err):  
      30         if self.isDeprecated(err):  
      31             self.addDeprecated(test)  
      32         elif self.isSkip(err):  
      33             self.addSkip(test)  
      34         else:  
      35             self.capt = buffer.getvalue()  
      36             if self.conf.debugErrors:  
      37                 if self.conf.capture:  
      38                     end_capture()  
      39                 pdb.post_mortem(err[2])  
      40                 if self.conf.capture:  
      41                     start_capture()  
      42             self.resetBuffer()  
      43             call_plugins(self.conf.plugins, 'addError',  
      44                          test, err, self.capt)  
      45              
      46     def addFailure(self, test, err):  
      47         self.capt = buffer.getvalue()  
      48         if self.conf.debugFailures:  
      49             if self.conf.capture:  
      50                 end_capture()  
      51             pdb.post_mortem(err[2])  
      52             if self.conf.capture:  
      53                 start_capture()  
      54         if self.conf.detailedErrors:  
      55             try:  
      56                 self.tbinfo = inspect_traceback(err[2])  
      57             except tokenize.TokenError:  
      58                 self.tbinfo = "ERR: unable to inspect traceback"  
      59         else:  
      60             self.tbinfo = ''  
      61         self.resetBuffer()  
      62         call_plugins(self.conf.plugins, 'addFailure',  
      63                      test, err, self.capt, self.tbinfo)  
      64          
      65     def addSkip(self, test):  
      66         self.resetBuffer()  
      67         call_plugins(self.conf.plugins, 'addSkip', test)  
    17 68  
      69     def addSuccess(self, test):  
      70         self.resetBuffer()  
      71         call_plugins(self.conf.plugins, 'addSuccess', test)  
      72  
      73     def isDeprecated(self, err):  
      74         if err[0] is DeprecatedTest:  
      75             return True  
      76         # FIXME also if is subclass or instance of DeprecatedTest  
      77  
      78         return False  
      79      
      80     def isSkip(self, err):  
      81         if err[0] is SkipTest:  
      82             return True  
      83         # FIXME also if is subclass or instance of DeprecatedTest  
    18 84  
    19   class TextTestResult(_TextTestResult):  
      85         return False  
      86          
      87     def resetBuffer(self):  
      88         buffer.truncate(0)         
      89  
      90     def startTest(self, test):  
      91         if self.conf.capture:  
      92             self.resetBuffer()  
      93             self.capt = None  
      94         self.tbinfo = None  
      95         call_plugins(self.conf.plugins, 'startTest', test)  
      96          
      97     def stopTest(self, test):  
      98         if self.conf.capture:  
      99             self.resetBuffer()  
      100             self.capt = None  
      101         self.tbinfo = None             
      102         call_plugins(self.conf.plugins, 'stopTest', test)  
      103  
      104  
      105 class TextTestResult(Result, _TextTestResult):  
    20 106     """Text test result that extends unittest's default test result with  
    21 107     several optional features:  
     
    48 134                  
    49 135     def addDeprecated(self, test):  
      136         super(TextTestResult, self).addDeprecated(test)  
    50 137         self.deprecated.append((test, '', ''))  
    51           self.resetBuffer()  
    52 138         self.writeRes('DEPRECATED','D')  
    53           call_plugins(self.conf.plugins, 'addDeprecated', test)  
    54 139          
    55 140     def addError(self, test, err):  
    56           if err[0] is DeprecatedTest:  
    57               self.addDeprecated(test)  
    58           elif err[0] is SkipTest:  
    59               self.addSkip(test)  
    60           else:  
    61               capt = buffer.getvalue()  
    62               if self.conf.debugErrors:  
    63                   if self.capture:  
    64                       end_capture()  
    65                   pdb.post_mortem(err[2])  
    66                   if self.capture:  
    67                       start_capture()  
      141         super(TextTestResult, self).addError(test, err)  
      142         if not self.isDeprecated(err) and not self.isSkip(err):  
    68 143             self.errors.append((test,  
    69 144                                 self._exc_info_to_string(err, test),  
    70                                   capt))  
    71               self.resetBuffer()  
      145                                 self.capt))  
    72 146             self.writeRes('ERROR','E')  
    73               call_plugins(self.conf.plugins, 'addError', test, err, capt)  
    74 147              
    75 148     def addFailure(self, test, err):  
    76           capt = buffer.getvalue()  
    77           if self.conf.debugFailures:  
    78               if self.capture:  
    79                   end_capture()  
    80               pdb.post_mortem(err[2])  
    81               if self.capture:  
    82                   start_capture()  
    83           if self.conf.detailedErrors:  
    84               try:  
    85                   tb = inspect_traceback(err[2])  
    86               except tokenize.TokenError:  
    87                   tb = "ERR: unable to inspect traceback"  
    88           else:  
    89               tb = ''  
      149         super(TextTestResult, self).addFailure(test, err)  
    90 150         self.failures.append((test,  
    91                                 self._exc_info_to_string(err, test) + tb,  
    92                                 capt))  
    93           self.resetBuffer()  
      151                               self._exc_info_to_string(err, test) + self.tbinfo,  
      152                               self.capt))  
    94 153         self.writeRes('FAIL','F')  
    95           call_plugins(self.conf.plugins, 'addFailure', test, err, capt, tb)  
    96 154          
    97 155     def addSkip(self, test):  
      156         super(TextTestResult, self).addSkip(test)  
    98 157         self.skip.append((test, '', ''))  
    99           self.resetBuffer()  
    100 158         self.writeRes('SKIP','S')  
    101           call_plugins(self.conf.plugins, 'addSkip', test)  
    102 159  
    103 160     def addSuccess(self, test):  
    104           self.resetBuffer()  
      161         super(TextTestResult, self).addSuccess(test)  
    104 161         self.writeRes('ok', '.')  
    105           call_plugins(self.conf.plugins, 'addSuccess', test)  
    106 162          
    107 163     def printErrors(self):  
     
    123 179                 self.stream.writeln(ln('>> end captured stdout <<'))  
    124 180  
    125       def resetBuffer(self):  
    126           buffer.truncate(0)         
    127    
    128 181     def startTest(self, test):  
    129           if self.capture:  
    130               self.resetBuffer()  
    131           super(TextTestResult, self).startTest(test)  
    132           call_plugins(self.conf.plugins, 'startTest', test)  
      182         Result.startTest(self, test)  
      183         _TextTestResult.startTest(self, test)  
    133 184          
    134 185     def stopTest(self, test):  
    135           if self.capture:  
    136               self.resetBuffer()  
    137           super(TextTestResult, self).stopTest(test)  
    138           call_plugins(self.conf.plugins, 'stopTest', test)  
      186         Result.stopTest(self, test)  
      187         _TextTestResult.stopTest(self, test)  
    139 188                  
    140 189     def writeRes(self, long, short):  
     
    148 197     """Start capturing output to stdout. DOES NOT reset the buffer.  
    149 198     """  
      199     stdout.append(sys.stdout)  
    150 200     sys.stdout = buffer  
    151 201  
     
    154 204     """Stop capturing output to stdout. DOES NOT reset the buffer.x  
    155 205     """  
    156       if sys.stdout is not stdout:  
    157           sys.stdout = stdout  
      206     if stdout:  
      207         sys.stdout = stdout.pop()  
    158 208          
    159 209      
    160   def install_patch(conf):  
    161       """Install TextTestResult as unittest._TextTestResult.  
    162       """  
    163       def result(stream, descriptions, verbosity, conf=conf):  
    164           return TextTestResult(stream, descriptions, verbosity, conf)  
    165       unittest._TextTestResult = result  
    166    
    167        
    168   def remove_patch():  
    169       """Reset unittest._TextTestResult to the default implementation  
    170       """  
    171       unittest._TextTestResult = _TextTestResult  
    172    
    173        
    174 210 def ln(label):  
    175 211     label_len = len(label) + 2  
  • trunk/nose/__init__.py

    r20 r21  
    283 283 from nose.core import *  
    284 284 from nose.exc import *  
    285   from nose.util import *  
    286   from nose.suite import LazySuite  
    287 285 from nose.loader import TestLoader  
      286 from nose.suite import LazySuite  
    288 287 from nose.result import TextTestResult  
      288 from nose.tools import with_setup # backwards compatibility  
      289 from nose.util import *  
    289 290  
    290 291 __author__ = 'Jason Pellerin'  
     
    294 295     'SkipTest', 'DeprecatedTest',  
    295 296     'TestCollector', 'TestLoader',  
    296       'collector', 'main', 'run_exit', 'with_setup',  
      297     'collector', 'main', 'run', 'run_exit', 'with_setup',  
    296 297     'file_like', 'split_test_name', 'test_address'  
    297 298     ]  
  • trunk/nose/selector.py

    r18 r21  
    5 5 from nose.config import Config  
    6 6 from nose.plugins import call_plugins  
    7   from nose.util import file_like, split_test_name, test_address  
      7 from nose.util import absfile, file_like, split_test_name, test_address  
    7 7  
    8 8 log = logging.getLogger(__name__)  
     
    50 50             containerMatch = False  
    51 51             if filename is not None:  
    52                   if sys.modules[clb.__module__].__file__ == filename:  
      52                 if not os.path.isabs(filename):  
      53                     filename = absfile(filename)  
      54                 log.debug("Check file match for callable %s in module %s",  
      55                           clb, clb.__module__)  
      56                 try:  
      57                     mod_file = sys.modules[clb.__module__].__file__  
      58                     if not os.path.isabs(mod_file):  
      59                         mod_file = absfile(mod_file, self.conf.where)