Skip to content

Commit

Permalink
add-suppressor-plugin (#32)
Browse files Browse the repository at this point in the history
* add-suppressor-plugin

* add-key-to-xunit-plugin
  • Loading branch information
z00sts authored Mar 6, 2018
1 parent a65cfe5 commit 8dc7f0f
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 24 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.4.4

- Add 'lode_runner.plugins.Suppressor' plugin. Allows suppress any exceptions in tearDown-methods

- Add optional parameter '--xunit-dump-suite-output'. If enabled drops TestSuite-level sysout/syserr to XUnit report.

## 0.4.3

- Add XUnit plugin 'lode_runner.plugins.ClassSkipper'. Allows skip TestClasses with no setUpClass calls
Expand Down
30 changes: 29 additions & 1 deletion lode_runner/core.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# coding: utf-8

import logging
from unittest import suite

from nose.core import TextTestResult, TextTestRunner, TestProgram
Expand All @@ -7,6 +10,9 @@
from nose.failure import Failure


log = logging.getLogger('lode_runner.core')


class ResultProxy(NoseResultProxy):
def assertMyTest(self, test):
pass
Expand Down Expand Up @@ -49,11 +55,20 @@ def __init__(self, *args, **kwargs):
super(TestLoader, self).__init__(*args, **kwargs)
self.suiteClass = ContextSuiteFactory(self.config, resultProxy=ResultProxyFactory(config=self.config))

def makeTest(self, obj, parent=None):
if getattr(self.config, "suppressTearDownExceptions", False):
obj_to_wrap = obj if isinstance(obj, type) else parent
if obj_to_wrap:
self._wrap_with_suppressor(obj_to_wrap)

return super(TestLoader, self).makeTest(obj, parent)

def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all tests cases contained in testCaseClass"""
if issubclass(testCaseClass, suite.TestSuite):
raise TypeError("Test cases should not be derived from TestSuite."
" Maybe you meant to derive from TestCase?")

test_case_names = self.getTestCaseNames(testCaseClass)
if not test_case_names and hasattr(testCaseClass, 'runTest'):
test_case_names = ['runTest']
Expand All @@ -72,6 +87,18 @@ def loadTestsFromModule(self, module, path=None, discovered=False):
return plugin_tests
return super(TestLoader, self).loadTestsFromModule(module, path=None, discovered=False)

def _wrap_with_suppressor(self, obj):
try:
from lode_runner.plugins.suppressor import suppress_exceptions
except ImportError:
log.exception('Error wrapping with lode_runner.plugins.suppressor ()')
return

names_list = ['tearDown'] + list(self.suiteClass.suiteClass.classTeardown)
methods_to_wrap = [getattr(obj, _name) for _name in names_list if hasattr(obj, _name)]
for _method in methods_to_wrap:
setattr(obj, _method.__name__, suppress_exceptions(_method))


class LodeTestResult(TextTestResult):
pass
Expand Down Expand Up @@ -113,9 +140,10 @@ def plugins():
from lode_runner.plugins.initializer import Initializer
from lode_runner.plugins.failer import Failer
from lode_runner.plugins.class_skipper import ClassSkipper
from lode_runner.plugins.suppressor import Suppressor

plugs = [
Dataprovider, Xunit, MultiProcess, TestId, Initializer, Failer, ClassSkipper
Dataprovider, Xunit, MultiProcess, TestId, Initializer, Failer, ClassSkipper, Suppressor
]

from nose.plugins import builtin
Expand Down
41 changes: 41 additions & 0 deletions lode_runner/plugins/suppressor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# coding: utf-8

import logging
from functools import wraps
from nose.plugins import Plugin

log = logging.getLogger('lode.plugins.suppressor')


def suppress_exceptions(func):
@wraps(func)
def wrapped_tear_down(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
log.exception('Suppressed error in {} {}'.format(func.__name__, func))

return wrapped_tear_down


class Suppressor(Plugin):
"""
Suppressor plugin: if enabled - suppress all exceptions in tearDown-like methods.
"""
name = 'suppressor'
enabled = True

def options(self, parser, env):
Plugin.options(self, parser, env)
parser.add_option(
'--suppress-teardown-exceptions',
action='store_true',
default=False,
help="Suppress any exceptions in tearDown/tearDownClass-like methods"
)

def configure(self, options, conf):
if not options.suppress_teardown_exceptions:
return

conf.suppressTearDownExceptions = options.suppress_teardown_exceptions
84 changes: 63 additions & 21 deletions lode_runner/plugins/xunit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@

from xml.etree import ElementTree

try:
from StringIO import StringIO
except ImportError:
from io import StringIO

from lode_runner.plugins import force_unicode_decorator

from nose.plugins.xunit import Xunit, force_unicode
from nose.plugins.xunit import Xunit as NoseXunit, force_unicode
from nose.plugins.base import Plugin


Expand All @@ -30,23 +25,67 @@ def __init__(self):
'skipped': 0})


class Xunit(Xunit):
class Xunit(NoseXunit):
_suite_stdout = None
_suite_stderr = None

def options(self, parser, env):
parser.add_option(
'--xunit-dump-suite-output', action='store_true', default=False,
help="If enabled, will dump suite-level sys-out and sys-err to XUnit report"
)
super(Xunit, self).options(parser, env)

def configure(self, options, config):
"""Configures the xunit plugin."""
Plugin.configure(self, options, config)
self.config = config
if self.enabled:
if hasattr(options, 'multiprocess_workers') and options.multiprocess_workers:
if multiprocessing.current_process().name == 'MainProcess':
Xunit.mp_context = MultiprocessContext()
self.stats = Xunit.mp_context.stats
self.errorlist = Xunit.mp_context.error_list
self.xunit_testsuite_name = options.xunit_testsuite_name
else:
super(Xunit, self).configure(options, config)
if not self.enabled:
return

self.xunit_dump_suite_output = options.xunit_dump_suite_output

if hasattr(options, 'multiprocess_workers') and options.multiprocess_workers:
if multiprocessing.current_process().name == 'MainProcess':
Xunit.mp_context = MultiprocessContext()
self.stats = Xunit.mp_context.stats
self.errorlist = Xunit.mp_context.error_list
self.xunit_testsuite_name = options.xunit_testsuite_name
else:
super(Xunit, self).configure(options, config)

self.error_report_filename = options.xunit_file

def _dump_suite_output(self):
if not self.xunit_dump_suite_output:
return

_captured_stdout = self._getCapturedStdout()
if _captured_stdout:
self._suite_stdout = _captured_stdout

_captured_stderr = self._getCapturedStderr()
if _captured_stderr:
self._suite_stderr = _captured_stderr

self._endCapture()

def beforeTest(self, test):
"""Initializes a timer before starting a test."""
test.id = force_unicode_decorator(test.id)
self._dump_suite_output()
super(Xunit, self).beforeTest(test)

def afterTest(self, test):
super(Xunit, self).afterTest(test)

if self.xunit_dump_suite_output:
self._startCapture()

def stopContext(self, context):
self._dump_suite_output()
super(Xunit, self).stopContext(context)

def report(self, stream):
"""Writes an Xunit-formatted XML file
Expand All @@ -65,13 +104,16 @@ def report(self, stream):
})
errors = [force_unicode(error) for error in self.errorlist]
[testsuite.append(ElementTree.fromstring(error.encode("utf-8"))) for error in errors]

if self.xunit_dump_suite_output:
if self._suite_stderr:
testsuite.append(ElementTree.fromstring(self._suite_stderr))

if self._suite_stdout:
testsuite.append(ElementTree.fromstring(self._suite_stdout))

ElementTree.ElementTree(testsuite).write(self.error_report_filename, encoding="utf-8", xml_declaration=True)

if self.config.verbosity > 1:
stream.writeln("-" * 70)
stream.writeln("XML: {}".format(os.path.abspath(self.error_report_filename)))

def beforeTest(self, test):
"""Initializes a timer before starting a test."""
test.id = force_unicode_decorator(test.id)
super(Xunit, self).beforeTest(test)
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
setup(
name='lode_runner',
url='https://github.com/2gis/lode_runner',
version='0.4.3',
version='0.4.4',
description='Nosetests runner plugins package',
long_description='',
author='Igor Pavlov',
Expand All @@ -26,7 +26,8 @@
'testid = lode_runner.plugins.testid:TestId',
'initializer = lode_runner.plugins.initializer:Initializer',
'failer = lode_runner.plugins.failer:Failer',
'class_skipper = lode_runner.plugins.class_skipper:ClassSkipper'
'class_skipper = lode_runner.plugins.class_skipper:ClassSkipper',
'suppressor = lode_runner.plugins.suppressor:Suppressor'
]
},
classifiers=[
Expand Down

0 comments on commit 8dc7f0f

Please sign in to comment.