from nose.plugins.multiprocess import MultiProcessTestRunner # @UnresolvedImport from nose.plugins.base import Plugin # @UnresolvedImport import sys from _pydev_runfiles import pydev_runfiles_xml_rpc import time from _pydev_runfiles.pydev_runfiles_coverage import start_coverage_support #======================================================================================================================= # PydevPlugin #======================================================================================================================= class PydevPlugin(Plugin): def __init__(self, configuration): self.configuration = configuration Plugin.__init__(self) def begin(self): # Called before any test is run (it's always called, with multiprocess or not) self.start_time = time.time() self.coverage_files, self.coverage = start_coverage_support(self.configuration) def finalize(self, result): # Called after all tests are run (it's always called, with multiprocess or not) self.coverage.stop() self.coverage.save() pydev_runfiles_xml_rpc.notifyTestRunFinished('Finished in: %.2f secs.' % (time.time() - self.start_time,)) #=================================================================================================================== # Methods below are not called with multiprocess (so, we monkey-patch MultiProcessTestRunner.consolidate # so that they're called, but unfortunately we loose some info -- i.e.: the time for each test in this # process). #=================================================================================================================== def report_cond(self, cond, test, captured_output, error=''): ''' @param cond: fail, error, ok ''' # test.address() is something as: # ('D:\\workspaces\\temp\\test_workspace\\pytesting1\\src\\mod1\\hello.py', 'mod1.hello', 'TestCase.testMet1') # # and we must pass: location, test # E.g.: ['D:\\src\\mod1\\hello.py', 'TestCase.testMet1'] try: if hasattr(test, 'address'): address = test.address() address = address[0], address[2] else: # multiprocess try: address = test[0], test[1] except TypeError: # It may be an error at setup, in which case it's not really a test, but a Context object. f = test.context.__file__ if f.endswith('.pyc'): f = f[:-1] elif f.endswith('$py.class'): f = f[:-len('$py.class')] + '.py' address = f, '?' except: sys.stderr.write("PyDev: Internal pydev error getting test address. Please report at the pydev bug tracker\n") import traceback;traceback.print_exc() sys.stderr.write("\n\n\n") address = '?', '?' error_contents = self.get_io_from_error(error) try: time_str = '%.2f' % (time.time() - test._pydev_start_time) except: time_str = '?' pydev_runfiles_xml_rpc.notifyTest(cond, captured_output, error_contents, address[0], address[1], time_str) def startTest(self, test): test._pydev_start_time = time.time() if hasattr(test, 'address'): address = test.address() file, test = address[0], address[2] else: # multiprocess file, test = test pydev_runfiles_xml_rpc.notifyStartTest(file, test) def get_io_from_error(self, err): if type(err) == type(()): if len(err) != 3: if len(err) == 2: return err[1] # multiprocess try: from StringIO import StringIO except: from io import StringIO s = StringIO() etype, value, tb = err import traceback;traceback.print_exception(etype, value, tb, file=s) return s.getvalue() return err def get_captured_output(self, test): if hasattr(test, 'capturedOutput') and test.capturedOutput: return test.capturedOutput return '' def addError(self, test, err): self.report_cond( 'error', test, self.get_captured_output(test), err, ) def addFailure(self, test, err): self.report_cond( 'fail', test, self.get_captured_output(test), err, ) def addSuccess(self, test): self.report_cond( 'ok', test, self.get_captured_output(test), '', ) PYDEV_NOSE_PLUGIN_SINGLETON = None def start_pydev_nose_plugin_singleton(configuration): global PYDEV_NOSE_PLUGIN_SINGLETON PYDEV_NOSE_PLUGIN_SINGLETON = PydevPlugin(configuration) return PYDEV_NOSE_PLUGIN_SINGLETON original = MultiProcessTestRunner.consolidate #======================================================================================================================= # new_consolidate #======================================================================================================================= def new_consolidate(self, result, batch_result): ''' Used so that it can work with the multiprocess plugin. Monkeypatched because nose seems a bit unsupported at this time (ideally the plugin would have this support by default). ''' ret = original(self, result, batch_result) parent_frame = sys._getframe().f_back # addr is something as D:\pytesting1\src\mod1\hello.py:TestCase.testMet4 # so, convert it to what report_cond expects addr = parent_frame.f_locals['addr'] i = addr.rindex(':') addr = [addr[:i], addr[i + 1:]] output, testsRun, failures, errors, errorClasses = batch_result if failures or errors: for failure in failures: PYDEV_NOSE_PLUGIN_SINGLETON.report_cond('fail', addr, output, failure) for error in errors: PYDEV_NOSE_PLUGIN_SINGLETON.report_cond('error', addr, output, error) else: PYDEV_NOSE_PLUGIN_SINGLETON.report_cond('ok', addr, output) return ret MultiProcessTestRunner.consolidate = new_consolidate