Changeset 9

Show
Ignore:
Timestamp:
Sun Mar 19 16:38:46 2006
Author:
jpellerin
Message:
  • Add example plugin
  • Work on plugin docs (#10)
  • Work on doc->wiki script (#4)
  • Fix for #15 (attrib plugin defaults should not be strings)
  • Fix for #5 (new assert introspection implemented)


Files:

Legend:

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

    r8 r9  
    645 645          
    646 646  
    647   def configure(argv=None, env=None):  
      647 def configure(argv=None, env=None, help=False):  
    647 647     """Configure the nose running environment. Execute configure before  
    648 648     collecting tests with nose.collector() to enable output capture and  
    649 649     other features.  
    650 650     """  
      651     if argv is None:  
      652         argv = sys.argv  
      653     if env is None:  
      654         env = os.environ  
      655      
    651 656     conf = Config()  
    652 657     parser = OptionParser(TestProgram.__doc__)  
     
    702 707                       help="Don't make any changes to sys.path when "  
    703 708                       "loading tests [NOSE_NOPATH]")  
      709  
    704 710      
    705 711     # add opts from plugins  
     
    714 720          
    715 721     options, args = parser.parse_args(argv)  
      722     if help:  
      723         return parser.format_help()  
    716 724      
    717 725     try:  
  • trunk/nose/plugins/doctests.py

    r5 r9  
    16 16 """  
    17 17 import doctest  
      18 import logging  
    18 19 import os  
    19 20 from nose.plugins.base import Collector, Selector  
    20   # from nose.selector import file_in_tests, module_in_tests, split_test_name  
    21   from nose.util import anyp, msg  
      21 from nose.util import anyp  
      22  
      23 log = logging.getLogger(__name__)  
    22 24  
    23 25 class Doctest(Collector, Selector):  
     
    26 28     extension = None  
    27 29      
    28       def add_options(self, parser, env):  
    29           super(Doctest, self).add_options(parser)  
      30     def add_options(self, parser, env=os.environ):  
      31         super(Doctest, self).add_options(parser, env)  
    30 32         try:  
    31 33             # 2.4 or better supports loading tests from non-modules  
     
    70 72     def tests_in_module(self, module):  
    71 73         if not self.matches(module.__name__):  
    72               msg("Doctest doesn't want module %s" % module, 4)  
      74             log.debug("Doctest doesn't want module %s", module)  
    72 74             return  
    73 75         try:  
    74 76             doctests = doctest.DocTestSuite(module)  
    75 77         except ValueError:  
    76               msg("No doctests in %s" % module, 4)  
      78             log.debug("No doctests in %s", module)  
    76 78             return  
    77 79         else:  
    78 80             # < 2.4 doctest (and unittest) suites don't have iterators  
    79               msg("Doctests found in %s" % module, 4)  
      81             log.debug("Doctests found in %s", module)  
    79 81             if hasattr(doctests, '__iter__'):  
    80 82                 doctest_suite = doctests  
  • trunk/nose/plugins/base.py

    r7 r9  
    91 91 class Watcher(Plugin):  
    92 92  
    93       def after(self, test):  
      93     def aftertest(self, test):  
    93 93         # call it result.stopTest  
    94 94         pass  
  • trunk/nose/plugins/cover.py

    r7 r9  
    11 11 .. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html  
    12 12 """  
      13 import logging  
    13 14 import os  
    14 15 import sys  
    15 16 from nose.plugins.base import Watcher  
    16   from nose.util import msg  
      17  
      18 log =  logging.getLogger(__name__)  
    17 19  
    18 20 class Coverage(Watcher):  
     
    30 32     cover_packages = None  
    31 33      
    32       def add_options(self, parser, env):  
      34     def add_options(self, parser, env=os.environ):  
    32 34         super(Coverage, self).add_options(parser, env)  
    33 35  
     
    50 52                 import coverage  
    51 53             except ImportError:  
    52                   print >> sys.stderr, "Coverage not available: " \  
    53                       "unable to import coverage module"  
      54                 log.error("Coverage not available: "  
      55                           "unable to import coverage module")  
      56                 self.enabled = False  
    54 57                 return  
    55 58         self.config = config  
     
    57 60         self.cover_packages = self.tolist(options.cover_packages)  
    58 61         if self.cover_packages:  
    59               msg("Coverage report will include only packages: %s"  
    60                   % self.cover_packages, 4)  
      62             log.info("Coverage report will include only packages: %s",  
      63                      self.cover_packages)  
    61 64              
    62 65     def begin(self):  
      66         log.debug("Coverage begin")  
    63 67         import coverage  
    64 68         self.skip_modules = sys.modules.keys()[:]  
    65 69         coverage.start()  
    66           msg("Coverage begin", 5)  
    67 70          
    68 71     def report(self, stream):  
    69           import coverage  
    70           msg("Coverage report", 5)  
      72         log.debug("Coverage report")  
      73         import coverage         
    71 74         modules = [ module  
    72 75                     for name, module in sys.modules.items()  
     
    76 79     def want_module_coverage(self, name, module):  
    77 80         if not hasattr(module, '__file__'):  
    78               msg("no coverage of %s: no __file__" % name, 5)  
      81             log.debug("no coverage of %s: no __file__", name)  
    78 81             return False  
    79 82         root, ext = os.path.splitext(module.__file__)  
    80 83         if not ext in ('.py', '.pyc', '.pyo'):  
    81               msg("no coverage of %s: not a python file" % name, 5)  
      84             log.debug("no coverage of %s: not a python file", name)  
    81 84             return False  
    82 85         if self.cover_packages:  
     
    87 90                     and (self.cover_tests  
    88 91                          or not self.config.testMatch.search(name))):  
    89                       msg("coverage for %s" % name, 5)  
      92                     log.debug("coverage for %s", name)  
    89 92                     return True                 
    90 93         if name in self.skip_modules:  
    91               msg("no coverage for %s: loaded before coverage start"  
    92                   % name, 5)  
      94             log.debug("no coverage for %s: loaded before coverage start",  
      95                       name)  
    93 96             return False  
    94 97         if self.config.testMatch.search(name) and not self.cover_tests:  
    95               msg("no coverage for %s: is a test" % name, 5)  
      98             log.debug("no coverage for %s: is a test", name)  
    95 98             return False  
    96 99         # accept any package that passed the previous tests, unless  
  • trunk/nose/plugins/__init__.py

    r7 r9  
    1   """FIXME docs on plugins  
      1 """nose plugins  
    1 1  
    2    - classes to subclass  
    3      
      2 nose supports setuptools entry point plugins for test collection,  
      3 selection, observation and reporting.  
      4  
      5 Writing Plugins  
      6 ---------------  
      7  
      8 Plugin classes should subclass nose.plugins.Plugin or one of the other  
      9 classes in nose.plugins.base.  
      10  
      11 Plugins may implement any of the methods in any or all of the interfaces  
      12 described in nose.plugins.base.  
      13  
      14 Registering  
      15 ===========  
      16  
      17 For nose to find a plugin, it must be part of a package that uses  
      18 setuptools, and the plugin must be included in the entry points defined  
      19 in the setup.py for the package::  
      20  
      21   setup(name='Some plugin',  
      22         ...  
      23         entry_points = {  
      24             'nose.plugins': [  
      25                 'someplugin = someplugin:SomePlugin'  
      26                 ]  
      27             },  
      28         ...  
      29         )  
      30  
      31 Once the package is installed with install or develop, nose will be able  
      32 to load the plugin.  
      33  
      34 Defining options  
      35 ================  
      36  
      37 All plugins must implement the methods `add_options(self, parser, env)`  
      38 and `configure(self, options, conf)`. Subclasses of nose.plugins.Plugin  
      39 that want the standard options should call the superclass methods.  
      40  
      41 defining command-line and environment options  
      42  
      43 configuring from selected options  
      44  
      45 logging  
      46  
      47 Examples  
      48 ========  
      49  
      50 See nose.plugins.attrib, nose.plugins.cover, nose.plugins.doctests and  
      51 nose.plugins.profile for examples.  
      52  
      53 examples in examples/ dir...  
    4 54 """  
    5 55 import logging  
     
    11 61  
    12 62 def call_plugins(plugins, method, *arg, **kw):  
      63     """Call all method on plugins in list, that define it, with provided  
      64     arguments. The first response that is not None is returned.  
      65     """  
    13 66     for plug in plugins:  
    14 67         func = getattr(plug, method, None)  
     
    22 75          
    23 76 def load_plugins(builtin=True, others=True):  
      77     """Load plugins, either builtin, others, or both.  
      78     """  
    24 79     for ep in pkg_resources.iter_entry_points('nose.plugins'):  
    25 80         log.debug("load plugin %s" % ep)  
  • trunk/nose/plugins/profile.py

    r4 r9  
      1 """This plugin is not yet functional.  
      2  
      3 FIXME  
      4  
      5 Need to figure out the least invasive, most general point to add a hook  
      6 where a plugin can be passed the test to be executed and return a  
      7 wrapper (or another callable of any kind).  
      8  
      9 FIXME  
      10 """  
      11  
      12 import hotshot, hotshot.stats  
    1 13 from nose.plugins.base import Watcher  
    2 14  
    3 15 class Profile(Watcher):  
    4       pass  
      16  
      17     # FIXME formatting options -- sort, restrict func names, % of list  
      18     # FIXME prof data filename option  
      19     # FIXME print/no print report  
      20      
      21     def begin(self):  
      22         self.pfile = 'nosetests.prof'  
      23         self.prof = hotshot.Profile(self.pfile)  
      24         self.prof.start()  
      25  
      26     def report(self, stream):  
      27         # FIXME sort  
      28         # FIXME how to print to the stream?  
      29         # FIXME only if wanted  
      30  
      31         self.pct = 10  
      32          
      33         self.prof.close()  
      34         stats = hotshot.stats.load(self.pfile)  
      35         stats.print_stats(self.pct)  
  • trunk/nose/plugins/attrib.py

    r7 r9  
    53 53         parser.add_option("-a", "--attr",  
    54 54                           dest="attr", action="append",  
    55                             default=env.get('NOSE_ATTR', ""),  
      55                           default=env.get('NOSE_ATTR'),  
    55 55                           help="Run only tests that have attributes "  
    56 56                           "specified by ATTR [NOSE_ATTR]")  
    57 57         parser.add_option("-A", "--eval-attr",  
    58 58                           dest="eval_attr", metavar="EXPR", action="append",  
    59                             default=env.get('NOSE_EVAL_ATTR', ""),  
      59                           default=env.get('NOSE_EVAL_ATTR'),  
    59 59                           help="Run only tests for whose attributes "  
    60 60                           "the Python expression EXPR evaluates "  
  • trunk/nose/result.py

    r8 r9  
    141 141  
    142 142 def start_capture():  
    143       """Start capturing output to stdout  
      143     """Start capturing output to stdout. DOES NOT reset the buffer.  
    143 143     """  
    144 144     sys.stdout = buffer  
     
    147 147  
    148 148 def end_capture():  
    149       """Stop capturing output to stdout  
      149     """Stop capturing output to stdout. DOES NOT reset the buffer.x  
    149 149     """  
    150 150     if sys.stdout is not stdout:  
  • trunk/nose/__init__.py

    r4 r9  
    2 2  
    3 3 nose provides an alternate test discovery and running process for  
    4   unittest, one that is intended to mimic the behavior of py.test as much as  
    5   is reasonably possible without resorting to too much magic.  
      4 unittest, one that is intended to mimic the behavior of py.test as much  
      5 as is reasonably possible without resorting to too much magic.  
    6 6  
    7 7 Basic usage  
    8   ===========  
      8 -----------  
    8 8  
    9 9 Use the nosetests script (after installation by setuptools)::  
     
    31 31    
    32 32 Features  
    33   ========  
      33 --------  
    33 33  
    34 34 Run as collect  
      35 ==============  
    35 36  
    36   nose begins running tests as soon as the first test module is  
    37   loaded, it does not wait to collect all tests before running the first.  
      37 nose begins running tests as soon as the first test module is loaded, it  
      38 does not wait to collect all tests before running the first.  
    38 39  
    39 40 Output capture  
      41 ==============  
    40 42  
    41   Unless called with the -s (--nocapture) switch, nose will capture  
    42   stdout during each test run, and print the captured output only for  
    43   tests that fail or have errors. The captured output is printed immediately  
      43 Unless called with the -s (--nocapture) switch, nose will capture stdout  
      44 during each test run, and print the captured output only for tests that  
      45 fail or have errors. The captured output is printed immediately  
    44 46 following the error or failure output for the test. (Note that output in  
    45 47 teardown methods is captured, but can't be output with failing tests,  
    46 48 because teardown has not yet run at the time of the failure.)  
    47 49  
    48   As of nose 0.7, output capture is implemented by patching a custom  
    49   Exception class into exceptions, which allows it to work with the default  
    50   unittest test runner, so you can use nose.collector to replace any  
    51   unittest.TestSuite in any context (in theory).  
      50 When running as a setup command, output capture is implemented by  
      51 patching nose's test result class into unittest.  
    52 52  
    53 53 Assert introspection  
      54 ====================  
    54 55  
    55 56 When run with the -d (--detailed-errors) switch, nose will try to output  
     
    64 62  
    65 63 In other words if you have a test like::  
    66    
      64    
    66 64   def test_integers():  
    67 65       a = 2  
     
    75 73   InspectAssertionError: assert 2 is 4  
    76 74     >>  assert 2 == 4, "assert 2 is 4"  
      75  
      76 When running as a setuptools command, assert introspection is  
      77 implemented by patching nose's test result class into unittest.  
    77 78      
    78 79 Setuptools integration  
      80 ======================  
    79 81  
    80   nose may be used with setuptools_. Simply specify the default test  
    81   collector as the test suite in your setup file::  
      82 nose may be used with the setuptools_ test command. Simply specify the  
      83 test collector as the test suite in your setup file::  
    82 84  
    83 85   setup (  
     
    97 98  
    98 99 Writing tests  
    99   =============  
      100 -------------  
    99 100  
    100 101 As with py.test and TestGears, nose tests need not be subclasses of  
     
    104 105 also matches that expression will be run as a test. For the sake of  
    105 106 compatibility with legacy unittest test cases, nose will also load tests from  
    106   unittest.TestCase subclasses just like unittest does, and wrap them in  
    107   nose.Wrap so that output capture will work for them too. Like py.test and  
      107 unittest.TestCase subclasses just like unittest does. Like py.test and  
    108 108 TestGears, functional tests will be run in the order in which they appear in  
    109 109 the module file. TestCase derived tests and other test classes are run in  
     
    111 111  
    112 112 Fixtures  
      113 ========  
    113 114  
    114 115 nose supports fixtures (setup and teardown methods) at the package,  
     
    120 120  
    121 121 Test packages  
      122 =============  
    122 123  
    123 124 nose allows tests to be grouped into test packages. This allows  
     
    136 136  
    137 137 Test modules  
      138 ============  
    138 139  
    139 140 A test module is a python module that matches the testMatch regular  
     
    145 145  
    146 146 Test classes  
      147 ============  
    147 148  
    148 149 A test class is a class defined in a test module that is either a subclass  
     
    156 156  
    157 157 Test functions  
      158 ==============  
    158 159  
    159 160 Any function in a test module that matches testMatch will be wrapped in a  
     
    192 192   test = with_setup(setup_func,teardown_func)(test)  
    193 193  
      194 or::  
      195  
      196   test.setup = setup_func  
      197   test.teardown = teardown_func  
      198    
    194 199 Test generators  
      200 ===============  
    195 201  
    196 202 nose supports test functions that are generators. A simple example from nose's  
     
    222 227        
    223 228 About the name  
    224   ==============  
      229 --------------  
    224 229  
    225 230 * nose is the least silly short synonym for discover in the dictionary.com  
     
    231 236  
    232 237 Contact the author  
    233   ==================  
      238 ------------------  
    233 238  
    234 239 To report bugs, ask questions, or request features, please email the author at  
     
    237 242  
    238 243 Similar test runners  
    239   ====================  
      244 --------------------  
    239 244  
    240 245 nose was inspired mainly by py.test_, which is far more functional than  
     
    255 260  
    256 261 License and copyright  
    257   =====================  
      262 ---------------------  
    257 262  
    258 263 nose is copyright Jason Pellerin 2005-2006  
     
    274 279  
    275 280 TODO  
    276   ====  
      281 ----  
    276 281  
    277   * Convert doctest and coverage support into optional plugins using setuptools  
    278     entrypoints  
    279 282 * Docstrings for all classes  
    280 283 * Output better shortDescription()s  
    281   * Change msg() signature to msg(message, args, level)  
    282 284 """  
    283 285  
    284 286 from nose.core import *  
      287 from nose.exc import *  
      288 from nose import result, plugins, selector  
    285 289  
    286 290 __author__ = 'Jason Pellerin'  
    287 291 __version__ = '0.9.0a1'  
    288   __all__ = [ 'core', 'exception', 'TestCase', 'TestCollector',  
    289               'TestLoader', 'collector', 'main', 'run_exit', 'with_setup' ]  
      292 __all__ = [ 'core', 'result', 'plugins', 'selector',  
      293             'SkipTest', 'DeprecatedTest', 'TestCase', 'TestCollector',  
      294             'collector', 'main', 'run_exit', 'with_setup' ]  
  • trunk/nose/inspector.py

    r7 r9  
    14 14      
    15 15 def inspect_traceback(tb):  
      16     # FIXME 10 lines might be enough, or... ?  
    16 17     frames = inspect.getinnerframes(tb, 10)  
    17 18     for frame in frames:  
     
    19 20         # print inspect.getframeinfo(frame[0])  
    20 21         exp = Expander(frame[0].f_locals, frame[0].f_globals)  
    21           src = StringIO(textwrap.dedent(''.join(frame[4][frame[5]:-3])))  
    22           tokenize.tokenize(src.readline, exp)  
    23       return exp.expanded_source  
    24 22  
      23         # figure out the set of lines to grab.  
      24         inspect_lines, mark_line = find_inspectable_lines(frame[4], frame[5])  
      25         src = StringIO(textwrap.dedent(''.join(inspect_lines)))  
      26  
      27         # FIXME somehow want to mark the actual assert line with a sigil  
    25 28          
      29         # if a token error results, try just doing the one line,  
      30         # stripped of any \ it might have  
      31         try:  
      32             tokenize.tokenize(src.readline, exp)  
      33         except tokenize.TokenError:  
      34             pass  
      35  
      36     if exp.expanded_source:  
      37         exp_lines = exp.expanded_source.split('\n')  
      38         padded = []  
      39         ep = 0  
      40         for line in exp_lines:  
      41             if ep == mark_line:  
      42                 padded.append('>>  ' + line)  
      43             else:  
      44                 padded.append('    ' + line)  
      45             ep += 1  
      46         return '\n'.join(padded)  
      47  
      48  
      49 def find_inspectable_lines(lines, pos):  
      50     """Find lines in home that are inspectable.  
      51      
      52     Walk back from the err line up to 3 lines, but don't walk back over  
      53     changes in indent level.  
      54  
      55     Walk forward up to 3 lines, counting \ separated lines as 1 don't walk  
      56     over changes in indent level (unless part of an extended line)  
      57     """  
      58     cnt = re.compile(r'\\[\s\n]*$')  
      59     df = re.compile(r':[\s\n]*$')  
      60     ind = re.compile(r'^(\s*)')  
      61     inspect = []  
      62     home = lines[pos]  
      63     home_indent = ind.match(home).groups()[0]  
      64      
      65     before = lines[max(pos-3, 0):pos]  
      66     before.reverse()  
      67     after = lines[pos+1:min(pos+4, len(lines))]  
      68  
      69     # print "before", before  
      70     # print "after", after  
      71     for line in before:  
      72         if ind.match(line).groups()[0] == home_indent:  
      73             inspect.append(line)  
      74         else:  
      75             break  
      76     inspect.reverse()  
      77     # print "before to inspect", inspect  
      78  
      79     inspect.append(home)  
      80     home_pos = len(inspect)-1  
      81     continued = cnt.search(home)  
      82     for line in after:  
      83         if ((continued or ind.match(line).groups()[0] == home_indent)  
      84             and not df.search(line)):  
      85             inspect.append(line)  
      86             continued = cnt.search(line)  
      87         else:  
      88             break  
      89     # print inspect  
      90     return inspect, home_pos  
      91      
    26 92 class Expander:  
    27 93     """Simple expression expander. Uses tokenize to find the names and  
     
    54 120             self.expanded_source += ' ' * (start[1]-self.lpos)  
    55 121         elif start[1] < self.lpos:  
    56               # newline  
    57               self.expanded_source += '\n'  
      122             # newline, indent correctly  
    58 123             self.expanded_source += ' ' * start[1]  
    59 124         self.lpos = end[1]  
     
    62 127             pass  
    63 128         elif ttype == tokenize.NAME:  
      129             # Clean this junk up  
    64 130             try:  
    65                   val = repr(self.locals[tok])  
      131                 val = self.locals[tok]  
      132                 if callable(val):  
      133                     val = tok  
      134                 else:  
      135                     val = repr(val)  
    66 136             except KeyError:  
    67 137                 try:  
    68                       val = repr(self.globals[tok])  
      138                     val = self.globals[tok]  
      139                     if callable(val):  
      140                         val = tok  
      141                     else:  
      142                         val = repr(val)  
      143  
    69 144                 except KeyError:  
    70 145                     val = tok  
      146             # FIXME... not sure how to handle things like funcs, classes  
    71 147             # FIXME this is broken for some unicode strings  
    72 148             self.expanded_source += val  
     
    74 150             self.expanded_source += tok  
    75 151         # FIXME if this is the end of the line and the line ends with  
    76           # \, then tack a \ onto the output  
      152         # \, then tack a \ and newline onto the output  
    76 152         # print line[end[1]:]  
    77 153         if re.match(r'\s+\\\n', line[end[1]:]):  
    78               self.expanded_source += ' \\'  
      154             self.expanded_source += ' \\\n'  
  • trunk/ez_setup.py

    r4 r9  
    15 15 """  
    16 16 import sys  
    17   DEFAULT_VERSION = "0.6a5"  
      17 DEFAULT_VERSION = "0.6a10"  
    17 17 DEFAULT_URL     = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]  
    18 18  
     
    23 23     'setuptools-0.6a1-py2.3.egg': 'ee819a13b924d9696b0d6ca6d1c5833d',  
    24 24     'setuptools-0.6a1-py2.4.egg': '8256b5f1cd9e348ea6877b5ddd56257d',  
      25     'setuptools-0.6a10-py2.3.egg': '162d8357f1aff2b0349c6c247ee62987',  
      26     'setuptools-0.6a10-py2.4.egg': '803a2d8db501c1ac3b5b6fb4e907f788',  
      27     's