Changeset 24

Show
Ignore:
Timestamp:
Wed Apr 19 20:34:11 2006
Author:
jpellerin
Message:

Documentation and 2.3 compatibility updates

Files:

Legend:

Unmodified
Added
Removed
Modified
  • trunk/AUTHORS

    r6 r24  
    1 1 Jason Pellerin  
    2 2 Kumar McMillan  
    3   Mika Eloranta  
      3 Mika Eloranta  
      4 Jay Parlar  
      5 Scot Doyle  
  • trunk/CHANGELOG

    r4 r24  
      1 0.9.0a1  
      2  
      3 - Add support for plugins, with hooks for selecting, loading and reporting on  
      4   tests. Doctest and coverage are now plugins.  
      5 - Add builtin plugins for profiling with hotshot, selecting tests by  
      6   attribute (contributed by Mika Eloranta), and warning of missed tests  
      7   specified on command line.  
      8 - Change command line test selection syntax to match unittest. Thanks to Titus  
      9   Brown for the suggestion.  
      10 - Option to drop into pdb on error or failure.  
      11 - Option to stop running on first error or failure. Thanks to Kevin Dangoor  
      12   for the suggestion.  
      13 - Support for doctests in files other than python modules (python 2.4 only)  
      14 - Reimplement base test selection as single self-contained class.  
      15 - Reimplement test loading as unittest-compatible TestLoader class.  
      16 - Remove all monkeypatching.  
      17 - Reimplement output capture and assert introspection support in  
      18   unittest-compatible Result class.  
      19 - Better support for multiline constructions in assert introspection.  
      20 - More context output with assert introspections.  
      21 - Refactor setuptools test command support to use proxied result, which  
      22   enables output capture and assert introspection support without  
      23   monkeypatching.  
      24 - Add support for generators in test classes. Thanks to Jay Parlar for the  
      25   suggestion and patch.  
      26 - Add nose.tools package with some helpful test-composition functions and  
      27   decorators, including @raises, contributed by Scot Doyle.  
      28 - Reimplement nose.main (TestProgram) to have unittest-compatible signature.  
      29 - All-new import path handling. You can even turn it off! (If you don't,  
      30   nose will ensure that all directories from which it imports anything are on  
      31   sys.path before the import.)  
      32 - Logging package used for verbose logging.  
      33 - Support for skipped and deprecated tests.  
      34 - Configuration is no longer global.  
      35  
    1 36 0.8.7  
    2 37  
  • trunk/unit_tests/test_loader.py

    r23 r24  
    6 6 from nose.importer import _import  
    7 7  
      8 from helpers import iter_compat  
    8 9 from mock import *  
    9 10  
     
    62 63         found = []  
    63 64         tests = l.loadTestsFromNames(['test', 'foo.test_foo'])  
    64           for t in tests:             
      65         for t in iter_compat(tests):  
    64 65             found.append(str(t))  
    65 66         self.assertEqual(found, expect)  
    66 67          
    67 68         expect = [ 'test module foo in %s' % self.support ]  
    68           # found = []  
    69 69         tests = l.loadTestsFromNames(None, module=foo)  
    70 70         found = [ str(tests) ]  
    71           #for t in tests:  
    72           #    found.append(str(t))  
    73 71         self.assertEqual(found, expect)  
    74 72  
    75           #found = []  
    76           #Etests = l.loadTestsFromNames(None, module=foo)  
    77           #Efor t in tests:  
    78           #    found.append(str(t))  
    79           #self.assertEqual(found, expect)  
    80 73  
    81 74     def test_load_from_names_compat(self):  
     
    93 86         found = []  
    94 87         # print tests  
    95           for test in tests:  
      88         for test in iter_compat(tests):  
    95 88             # print test  
    96               for t in test:  
      89             for t in iter_compat(test):  
    96 89                 # print t  
    97 90                 found.append(str(t))  
     
    105 98         names[0] = ':' + names[0]  
    106 99         tests = l.loadTestsFromNames(names, sys.modules[__name__])  
    107           for test in tests:  
    108               for t in test:  
      100         for test in iter_compat(tests):  
      101             for t in iter_compat(test):  
    109 102                 found.append(str(t))  
    110 103         self.assertEqual(found, expect)  
     
    168 161         cases = l.loadTestsFromTestCase(TC)  
    169 162         count = 0  
    170           for suite in cases:  
    171               for case in suite:  
      163         for suite in iter_compat(cases):  
      164             for case in iter_compat(suite):  
    172 165                 assert str(case) == '%s.TC.test_generator:(%d,)' % \  
    173 166                     (__name__, count)  
     
    191 184         print cases  
    192 185         count = 0  
    193           for case in cases:  
      186         for case in iter_compat(cases):  
    193 186             # print case  
    194 187             self.assertEqual(str(case),  
     
    199 192          
    200 193 if __name__ == '__main__':  
    201       import logging  
    202       logging.basicConfig()  
    203       logging.getLogger('').setLevel(0)  
    204       unittest.main() # testLoader=loader.TestLoader())  
      194     #import logging  
      195     #logging.basicConfig()  
      196     #logging.getLogger('').setLevel(0)  
      197     unittest.main(testLoader=loader.TestLoader())  
  • trunk/unit_tests/test_inspector.py

    r21 r24  
    11 11     from StringIO import StringIO  
    12 12  
    13   from nose.inspector import inspect_traceback, Expander  
      13 from nose.inspector import inspect_traceback, Expander, tbsource  
    13 13  
    14 14 class TestExpander(unittest.TestCase):  
     
    23 23         tokenize.tokenize(src.readline, exp)  
    24 24         # print "'%s'" % exp.expanded_source  
    25           assert exp.expanded_source.strip() == '2 > 2'  
      25         self.assertEqual(exp.expanded_source.strip(), '2 > 2')  
    25 25  
    26 26     def test_inspect_traceback_continued(self):  
     
    35 35             out = inspect_traceback(tb)  
    36 36             # print "'%s'" % out.strip()  
    37               assert out.strip() == '>>  assert 6 < 1, \\\n        "This is a multline expression"'  
      37             self.assertEqual(out.strip(),  
      38                              '>>  assert 6 < 1, \\\n        '  
      39                              '"This is a multline expression"')  
    38 40  
      41     def test_get_tb_source_simple(self):  
      42         # no func frame  
      43         try:  
      44             assert False  
      45         except AssertionError:  
      46             et, ev, tb = sys.exc_info()  
      47             lines, lineno = tbsource(tb, 1)  
      48             self.assertEqual(''.join(lines).strip(), 'assert False')  
      49             self.assertEqual(lineno, 0)  
      50  
      51     def test_get_tb_source_func(self):         
      52         # func frame  
      53         def check_even(n):  
      54             print n  
      55             assert n % 2 == 0  
      56         try:  
      57             check_even(1)  
      58         except AssertionError:  
      59             et, ev, tb = sys.exc_info()  
      60             lines, lineno = tbsource(tb)  
      61             out = textwrap.dedent(''.join(lines))  
      62             self.assertEqual(out,  
      63                              '    print n\n'  
      64                              '    assert n % 2 == 0\n'  
      65                              'try:\n'  
      66                              '    check_even(1)\n'  
      67                              'except AssertionError:\n'  
      68                              '    et, ev, tb = sys.exc_info()\n'  
      69                              )  
      70             self.assertEqual(lineno, 3)  
      71              
      72         # FIXME 2 func frames  
      73              
    39 74     def test_pick_tb_lines(self):  
    40 75         try:  
     
    47 82             out = inspect_traceback(tb)  
    48 83             # print "'%s'" % out.strip()  
    49               assert out.strip() == ">>  assert defred('fred') == 'barney', " \  
    50                   '"Fred - fred != barney?"'  
      84             self.assertEqual(out.strip(),  
      85                              ">>  assert defred('fred') == 'barney', "  
      86                              '"Fred - fred != barney?"')  
    51 87         try:  
    52 88             val = "fred"  
     
    60 96             et, ev, tb = sys.exc_info()  
    61 97             out = inspect_traceback(tb)  
    62               # print out  
    63               assert out.strip() == ">>  assert defred('fred') == 'barney', " \  
    64                   '\\\n        "Fred - fred != barney?"'  
      98             #print "'%s'" % out.strip()  
      99             self.assertEqual(out.strip(),  
      100                              ">>  assert defred('fred') == 'barney', "  
      101                              '\\\n        "Fred - fred != barney?"')  
    65 102  
    66 103         S = {'setup':1}  
     
    74 111             et, ev, tb = sys.exc_info()  
    75 112             out = inspect_traceback(tb)  
    76               # print "'%s'" % out.strip()  
    77               assert out.strip() == "assert {'setup': 1}['setup']\n" \  
    78                   "    print 1, 3\n"\  
    79                   ">>  assert 1 % 2 == 0 or 3 % 2 == 0"  
      113             print "'%s'" % out.strip()  
      114             self.assertEqual(out.strip(),  
      115                              "assert {'setup': 1}['setup']\n"  
      116                              "    print 1, 3\n"  
      117                              ">>  assert 1 % 2 == 0 or 3 % 2 == 0")  
    80 118              
    81 119          
    82 120 if __name__ == '__main__':  
    83       import logging  
    84       logging.basicConfig()  
    85       logging.getLogger('').setLevel(0)  
      121     #import logging  
      122     #logging.basicConfig()  
      123     #logging.getLogger('').setLevel(0)  
    86 124     unittest.main()  
    87 125      
  • trunk/unit_tests/test_result.py

    r23 r24  
    115 115         test = self.T()  
    116 116         tr.addError(test, err)         
    117           self.assertTrue(tr.shouldStop)  
      117         assert tr.shouldStop  
    117 117  
    118 118     def test_stop_on_error_fail(self):  
     
    126 126         test = self.T()  
    127 127         tr.addFailure(test, err)         
    128           self.assertTrue(tr.shouldStop)         
      128         assert tr.shouldStop  
    128 128          
    129 129 if __name__ == '__main__':  
  • trunk/unit_tests/test_plugins.py

    r23 r24  
    3 3 import unittest  
    4 4 import nose.plugins  
      5 from warnings import warn  
      6  
    5 7 from nose.config import Config  
    6 8 from nose.plugins.attrib import AttributeSelector  
     
    17 19     pass  
    18 20  
      21 # some plugins have 2.4-only features  
      22 pyvers = sys.version_info  
      23 compat_24 = pyvers[0] >= 2 and pyvers[1] >= 4  
      24  
    19 25 class TestBuiltinPlugins(unittest.TestCase):  
    20 26  
     
    86 92         o2, d2 = parser.opts[1]  
    87 93         assert o2[0] == '--doctest-tests'  
    88            
    89           o3, d3 = parser.opts[2]  
    90           assert o3[0] == '--doctest-extension'  
      94  
      95         if compat_24:  
      96             o3, d3 = parser.opts[2]  
      97             assert o3[0] == '--doctest-extension'  
      98         else:  
      99             assert len(parser.opts) == 2  
    91 100      
    92 101     def test_want_file(self):  
     
    129 138         plug.configure(opt, conf)  
    130 139         suite = plug.loadTestsFromModule(foo.bar.buz)  
    131           expect = ['afunc (foo.bar.buz)']  
      140         if compat_24:  
      141             expect = ['afunc (foo.bar.buz)']  
      142         else:  
      143             expect = ['unittest.FunctionTestCase (runit)']             
    132 144         for test in suite:  
    133               assert str(test) == expect.pop(0)  
      145             self.assertEqual(str(test), expect.pop(0))  
    133 145              
    134 146     def test_collect_txtfile(self):  
      147         if not compat_24:  
      148             warn("No support for doctests in files other than python modules"  
      149                  " in python versions older than 2.4")  
      150             return  
    135 151         here = os.path.abspath(os.path.dirname(__file__))  
    136 152         support = os.path.join(here, 'support')  
     
    182 198         plug.configure(opt, Config())  
    183 199         self.assertEqual(plug.attribs[0][0], 'weird >= 66')  
    184           self.assertTrue(callable(plug.attribs[0][1]))  
      200         assert callable(plug.attribs[0][1])  
    184 200                          
    185 201     def test_basic_attr(self):  
     
    197 213  
    198 214     def test_eval_attr(self):  
      215         if not compat_24:  
      216             warn("No support for eval attributes in python versions older"  
      217                  " than 2.4")  
      218             return  
    199 219         def f():  
    200 220             pass  
  • trunk/unit_tests/test_collector.py

    r23 r24  
    6 6 from nose.config import Config  
    7 7 from nose.result import TextTestResult  
      8 from helpers import iter_compat  
    8 9  
    9 10 class TestNoseCollector(unittest.TestCase):  
     
    26 27         found = []  
    27 28  
    28           for test in tc:  
      29         for test in iter_compat(tc):  
    28 29             found.append(str(test))  
    29 30         self.assertEqual(found, expect)  
     
    57 58         found = []  
    58 59  
    59           for test in tc:  
      60         for test in iter_compat(tc):  
    59 60             print test  
    60 61             found.append(str(test))  
    61 62             test.setUp()             
    62               for t in test:  
      63             for t in iter_compat(test):  
    62 63                 print ' ', t  
    63 64                 #test(rr)  
    64 65                 found.append(str(t))  
    65 66                 try:  
    66                       for tt in t:  
      67                     for tt in iter_compat(t):  
    66 67                         print '   ', tt  
    67 68                         found.append(str(tt))  
  • trunk/unit_tests/test_lazy_suite.py

    r21 r24  
    1 1 import unittest  
    2 2 from nose import LazySuite  
      3 from helpers import iter_compat  
    3 4  
    4 5 def gen():  
     
    14 15     def test_basic_iteration(self):         
    15 16         ls = LazySuite(gen)  
    16           for t in ls:  
      17         for t in iter_compat(ls):  
    16 17             assert isinstance(t, unittest.TestCase)  
    17 18              
  • trunk/nose/suite.py

    r23 r24  
    158 158  
    159 159     def loadtests(self):  
    160           for test in self._loadtests(self.module, self.path):  
    161               yield test     
      160         tests = self._loadtests(self.module, self.path)  
      161         try:  
      162             for test in tests:  
      163                 yield test  
      164         except TypeError:  
      165             # python 2.3: TestSuite not iterable  
      166             for test in tests._tests:  
      167                 yield test  
    162 168  
    163 169     def id(self):  
  • trunk/nose/plugins/attrib.py

    r15 r24  
    51 51     def add_options(self, parser, env=os.environ):  
    52 52         """Add command-line options for this plugin."""  
      53  
      54         # FIXME disable in < 2.4  
    53 55         parser.add_option("-a", "--attr",  
    54 56                           dest="attr", action="append",  
  • trunk/nose/result.py

    r23 r24  
    93 93          
    94 94     def resetBuffer(self):  
    95           buffer.truncate(0)         
      95         buffer.truncate(0)  
      96         buffer.seek(0)         
    96 97  
    97 98     def startTest(self, test):  
     
    201 202             self.stream.write(short)  
    202 203  
      204     def _exc_info_to_string(self, err, test):  
      205         try:  
      206             return _TextTestResult._exc_info_to_string(self, err, test)  
      207         except TypeError:  
      208             # 2.3: does not take test arg  
      209             return _TextTestResult._exc_info_to_string(self, err)  
    203 210  
      211          
    204 212 def start_capture():  
    205 213     """Start capturing output to stdout. DOES NOT reset the buffer.  
  • trunk/nose/config.py

    r23 r24  
    1   import copy  
    2 1 import os  
    3 2 import re  
     
    24 23         self.where=None  
    25 24         self.update(kw)  
    26           self._orig = copy.deepcopy(self.__dict__)  
      25         self._orig = self.__dict__.copy()  
    26 25          
    27 26     def __str__(self):  
  • trunk/nose/inspector.py

    r21 r24  
    6 6 import textwrap  
    7 7 import tokenize  
      8 import traceback  
    8 9  
    9 10 try:  
     
    13 14  
    14 15 log = logging.getLogger(__name__)  
    15        
      16  
    15 16 def inspect_traceback(tb):  
    16       # FIXME 10 lines might be enough, or... ?  
      17     """Inspect a traceback and its frame, returning source for the expression  
      18     where the exception was raised, with simple variable replacement performed  
      19     and the line on which the exception was raised marked with '>>'  
      20     """  
    17 21     log.debug('inspect traceback %s', tb)  
    18        
    19       frames = inspect.getinnerframes(tb, 10)  
    20    
    21       # FIXME when running under nosetests, context lines have gone missing  
    22       for frame in frames:  
    23           # print frame  
    24           # print inspect.getframeinfo(frame[0])  
    25    
    26           log.debug('inspect frame %s', frame)  
    27            
    28           exp = Expander(frame[0].f_locals, frame[0].f_globals)  
    29 22  
    30           # figure out the set of lines to grab.  
    31           inspect_lines, mark_line = find_inspectable_lines(frame[4], frame[5])  
    32           src = StringIO(textwrap.dedent(''.join(inspect_lines)))  
    33            
    34           # if a token error results, try just doing the one line,  
    35           # stripped of any \ it might have  
    36           try:  
    37               tokenize.tokenize(src.readline, exp)  
    38           except tokenize.TokenError:  
    39               pass  
      23     # we only want the innermost frame, where the exception was raised  
      24     while tb.tb_next:  
      25         tb = tb.tb_next  
      26          
      27     frame = tb.tb_frame  
      28     lines, exc_line = tbsource(tb)  
      29          
      30     # figure out the set of lines to grab.  
      31     inspect_lines, mark_line = find_inspectable_lines(lines, exc_line)  
      32     src = StringIO(textwrap.dedent(''.join(inspect_lines)))  
      33  
      34     # FIXME  
      35     # if a token error results, try just doing the one line,  
      36     # stripped of any \ it might have  
      37     exp = Expander(frame.f_locals, frame.f_globals)  
      38     try:  
      39         tokenize.tokenize(src.readline, exp)  
      40     except tokenize.TokenError:  
      41         pass  
    40 42  
      43     padded = []  
    41 44     if exp.expanded_source:  
    42 45         exp_lines = exp.expanded_source.split('\n')  
    43           padded = []  
    44 46         ep = 0  
    45 47         for line in exp_lines:  
     
    50 52                 padded.append('    ' + line)  
    51 53             ep += 1  
    52           return '\n'.join(padded)  
      54     return '\n'.join(padded)  
      55  
      56  
      57 def tbsource(tb, context=6):  
      58     """Get source from  a traceback object.  
    53 59  
      60     A tuple of two things is returned:  a list of lines of context from  
      61     the source code, and the index of the current line within that list.  
      62     The optional second argument specifies the number of lines of context  
      63     to return, which are centered around the current line.  
    54 64  
      65     NOTE:  
      66      
      67     This is taken from the python 2.4 standard library, since a bug in the 2.3  
      68     version of inspect prevents it from correctly locating source lines in a  
      69     traceback frame.  
      70     """  
      71     lineno = tb.tb_lineno  
      72     frame = tb.tb_frame  
      73  
      74     if context > 0:  
      75         start = lineno - 1 - context//2  
      76         try:  
      77             lines, lnum = inspect.findsource(frame)  
      78         except IOError:  
      79             lines = index = None  
      80         else:  
      81             start = max(start, 1)  
      82             start = max(0, min(start, len(lines) - context))  
      83             lines = lines[start:start+context]  
      84             index = lineno - 1 - start  
      85     else:  
      86         lines = index = None  
      87  
      88     return (lines, index)     
      89  
      90      
    55 91 def find_inspectable_lines(lines, pos):  
    56 92     """Find lines in home that are inspectable.  
     
    59 95     changes in indent level.  
    60 96  
    61       Walk forward up to 3 lines, counting \ separated lines as 1 don't walk  
      97     Walk forward up to 3 lines, counting \ separated lines as 1. Don't walk  
    61 97     over changes in indent level (unless part of an extended line)  
    62 98     """  
     
    65 101     df = re.compile(r':[\s\n]*$')  
    66 102     ind = re.compile(r'^(\s*)')  
    67       inspect = []  
      103     toinspect = []  
    67 103     home = lines[pos]  
    68 104     home_indent = ind.match(home).groups()[0]  
     
    73 109     after = lines[pos+1:min(pos+4, len(lines))]  
    74 110  
    75       # print "before", before  
    76       # print "after", after  
    77 111     for line in before:  
    78 112         if ind.match(line).groups()[0] == home_indent:  
    79               inspect.append(line)  
      113             toinspect.append(line)  
    79 113         else:  
    80 114             break  
    81       inspect.reverse()  
    82       # print "before to inspect", inspect  
    83    
    84       inspect.append(home)  
    85       home_pos = len(inspect)-1  
      115     toinspect.reverse()  
      116     toinspect.append(home)  
      117     home_pos = len(toinspect)-1  
    86 118     continued = cnt.search(home)  
    87 119     for line in after:  
    88 120         if ((continued or ind.match(line).groups()[0] == home_indent)  
    89 121             and not df.search(line)):  
    90               inspect.append(line)  
      122             toinspect.append(line)  
    90 122             continued = cnt.search(line)  
    91 123         else:  
    92 124             break  
    93       # print inspect  
    94       return inspect, home_pos  
    95        
      125     return toinspect, home_pos  
      126  
      127  
    96 128 class Expander:  
    97 129     """Simple expression expander. Uses tokenize to find the names and  
     
    155 187         else:  
    156 188             self.expanded_source += tok  
    157           # FIXME if this is the end of the line and the line ends with  
      189         # if this is the end of the line and the line ends with  
    157 189         # \, then tack a \ and newline onto the output  
    158 190         # print line[end[1]:]  
  • trunk/NEWS

    r4 r24  
    1   New in version 0.8  
    2    
    3     As of version 0.8, nose will try to discover and run doctest tests in  
    4     *non-test* packages that it loads. Doctests are run like any other  
    5     tests, except that doctest captures stdout itself, so nose is not able  
    6     to print captured output with failed doctests.  
    7      
      1 New in version 0.9.0a1  
      2 ----------------------  
    8 3  
    9     If you have Ned Batchelder's coverage_ module installed, you may print a  
    10     coverage report (after the test result output) with the -l or --coverage  
    11     switch or by setting the NOSE_COVERAGE environment variable. The  
    12     coverage report will cover any module that nose has imported, except  
    13     modules matching testMatch.  
    14    
    15    
    16     To aid in integrating nose into other scripts or modules, nose.main()  
    17     now returns the success or failure status of the test run, and does not  
    18     exit. This means that nose.main() behaves differently from  
    19     unittest.main(), so consider this change experimental. nose.run_exit()  
    20     has been added to support the old behavior, and the nosetests script  
    21     calls that function.  
    22      
      4 nose 0.9 is a major new release of nose. For this reason, I'm releasing it  
      5 first as an alpha version, 0.9.0a1. Between now and 0.9 final, only bugfixes  
      6 and documentation improvements will be applied to the stable tree.  
    23 7  
    24     Prior to this version, modules that defined a setup method called  
    25     'setUp' or 'setup' would see their setup method run twice, and teardown  
    26     method not run at all.     
      8 Here's a quick rundown on what's new in 0.9.  
    27 9  
    28   .. _doctest: http://docs.python.org/lib/module-doctest.html  
    29   .. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html  
      10 - Plugins!  
      11    
      12   The biggest change is support for plugins using setuptools entrypoints. nose  
      13   plugins can select and load tests (like the builtin doctest plugin), reject  
      14