diff options
author | Jason Pellerin <jpellerin@gmail.com> | 2008-06-06 02:34:25 +0000 |
---|---|---|
committer | Jason Pellerin <jpellerin@gmail.com> | 2008-06-06 02:34:25 +0000 |
commit | 5d2bd983da57f091d9337314a45ee8c49297dc22 (patch) | |
tree | eac29cc48f304f79f1ebcf900df76f501676c206 /nose | |
parent | 78b8d93eb22442db4427324f97bade1fe0ac9ff4 (diff) | |
download | nose-5d2bd983da57f091d9337314a45ee8c49297dc22.tar.gz |
Merged ticket-148 branch: added logcapture plugin. Bumped version to 0.11
Diffstat (limited to 'nose')
-rw-r--r-- | nose/__init__.py | 2 | ||||
-rw-r--r-- | nose/config.py | 2 | ||||
-rw-r--r-- | nose/plugins/__init__.py | 4 | ||||
-rw-r--r-- | nose/plugins/builtin.py | 1 | ||||
-rw-r--r-- | nose/plugins/capture.py | 2 | ||||
-rw-r--r-- | nose/plugins/logcapture.py | 124 |
6 files changed, 130 insertions, 5 deletions
diff --git a/nose/__init__.py b/nose/__init__.py index 9deb02c..6f1da47 100644 --- a/nose/__init__.py +++ b/nose/__init__.py @@ -397,7 +397,7 @@ from nose.exc import SkipTest, DeprecatedTest from nose.tools import with_setup __author__ = 'Jason Pellerin' -__versioninfo__ = (0, 10, 3) +__versioninfo__ = (0, 11, 0) __version__ = '.'.join(map(str, __versioninfo__)) __all__ = [ diff --git a/nose/config.py b/nose/config.py index 738ef48..e4f6712 100644 --- a/nose/config.py +++ b/nose/config.py @@ -317,7 +317,7 @@ class Config(object): # only add our default handler if there isn't already one there # this avoids annoying duplicate log messages. - if not logger.handlers: + if handler not in logger.handlers: logger.addHandler(handler) # default level diff --git a/nose/plugins/__init__.py b/nose/plugins/__init__.py index 91ea9a1..8f4329a 100644 --- a/nose/plugins/__init__.py +++ b/nose/plugins/__init__.py @@ -7,8 +7,8 @@ reporting. There are two basic rules for plugins: * Plugin classes should subclass `nose.plugins.Plugin`_. * Plugins may implement any of the methods described in the class - `PluginInterface`_ in nose.plugins.base. Please note that this class is for - documentary purposes only; plugins may not subclass PluginInterface. + `IPluginInterface`_ in nose.plugins.base. Please note that this class is for + documentary purposes only; plugins may not subclass IPluginInterface. .. _nose.plugins.Plugin: http://python-nose.googlecode.com/svn/trunk/nose/plugins/base.py diff --git a/nose/plugins/builtin.py b/nose/plugins/builtin.py index 3912e5e..b9b9fb8 100644 --- a/nose/plugins/builtin.py +++ b/nose/plugins/builtin.py @@ -5,6 +5,7 @@ plugins = [] builtins = ( ('nose.plugins.attrib', 'AttributeSelector'), ('nose.plugins.capture', 'Capture'), + ('nose.plugins.logcapture', 'LogCapture'), ('nose.plugins.cover', 'Coverage'), ('nose.plugins.debug', 'Pdb'), ('nose.plugins.deprecated', 'Deprecated'), diff --git a/nose/plugins/capture.py b/nose/plugins/capture.py index cf2a734..874a68e 100644 --- a/nose/plugins/capture.py +++ b/nose/plugins/capture.py @@ -1,7 +1,7 @@ """ This plugin captures stdout during test execution, appending any output captured to the error or failure output, should the test fail -or raise an error. It is enabled by default but may be disable with +or raise an error. It is enabled by default but may be disabled with the options -s or --nocapture. """ import logging diff --git a/nose/plugins/logcapture.py b/nose/plugins/logcapture.py new file mode 100644 index 0000000..a580171 --- /dev/null +++ b/nose/plugins/logcapture.py @@ -0,0 +1,124 @@ +""" +This plugin captures logging statements issued during test +execution, appending any output captured to the error or failure +output, should the test fail or raise an error. It is enabled by +default but may be disabled with the options --nologcapture. + +To remove any other installed logging handlers, use the +--logging-clear-handlers option. + +When an error or failure occurs, captures log messages are attached to +the running test in the test.capturedLogging attribute, and added to +the error failure output. + +Status: http://code.google.com/p/python-nose/issues/detail?id=148 +""" + +import os +import logging +from logging.handlers import BufferingHandler + +from nose.plugins.base import Plugin +from nose.util import ln + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +log = logging.getLogger(__name__) + + +class MyMemoryHandler(BufferingHandler): + def flush(self): + pass # do nothing + def truncate(self): + self.buffer = [] + + +class LogCapture(Plugin): + """ + Log capture plugin. Enabled by default. Disable with --nologcapture. + This plugin captures logging statement issued during test execution, + appending any output captured to the error or failure output, + should the test fail or raise an error. + """ + enabled = True + env_opt = 'NOSE_NOLOGCAPTURE' + name = 'logcapture' + score = 500 + logformat = '%(name)s: %(levelname)s: %(message)s' + clear = False + + def options(self, parser, env=os.environ): + parser.add_option( + "", "--nologcapture", action="store_false", + default=not env.get(self.env_opt), dest="logcapture", + help="Don't capture logging output [NOSE_NOLOGCAPTURE]") + parser.add_option( + "", "--logging-format", action="store", dest="logcapture_format", + default=env.get('NOSE_LOGFORMAT') or self.logformat, + help="logging statements formatting [NOSE_LOGFORMAT]") + parser.add_option( + "", "--logging-clear-handlers", action="store_true", + default=False, dest="logcapture_clear", + help="Clear all other logging handlers") + + def configure(self, options, conf): + self.conf = conf + # Disable if explicitly disabled, or if logging is + # configured via logging config file + if not options.logcapture or conf.loggingConfig: + self.enabled = False + self.logformat = options.logcapture_format + self.clear = options.logcapture_clear + + def setupLoghandler(self): + # setup our handler with root logger + root_logger = logging.getLogger() + if self.clear: + for handler in root_logger.handlers: + root_logger.removeHandler(handler) + # unless it's already there + if self.handler not in root_logger.handlers: + root_logger.addHandler(self.handler) + # to make sure everything gets captured + root_logger.setLevel(logging.NOTSET) + + def begin(self): + self.start() + + def start(self): + self.handler = MyMemoryHandler(1000) + fmt = logging.Formatter(self.logformat) + self.handler.setFormatter(fmt) + self.setupLoghandler() + + def end(self): + pass + + def beforeTest(self, test): + self.setupLoghandler() + + def afterTest(self, test): + self.handler.truncate() + + def formatFailure(self, test, err): + return self.formatError(test, err) + + def formatError(self, test, err): + # logic flow copied from Capture.formatError + test.capturedLogging = records = self.formatLogRecords() + if not records: + return err + ec, ev, tb = err + return (ec, self.addCaptureToErr(ev, records), tb) + + def formatLogRecords(self): + format = self.handler.format + return [format(r) for r in self.handler.buffer] + + def addCaptureToErr(self, ev, records): + return '\n'.join([str(ev), ln('>> begin captured logging <<')] + \ + records + \ + [ln('>> end captured logging <<')]) |