Commit 0f0c5b62 authored by Jérome Perrin's avatar Jérome Perrin

runUnitTest: display actual error when running a test component with error

Instead of displaying the same ImportError without any detail for both
cases of a non existant module or of an existing module which cause
error during import, improve the second case by displaying a different
message containing the original traceback.
parent 7e3bdfc2
...@@ -35,6 +35,7 @@ import sys ...@@ -35,6 +35,7 @@ import sys
import imp import imp
import collections import collections
from six import reraise from six import reraise
import traceback
import coverage import coverage
from Products.ERP5Type.Utils import ensure_list from Products.ERP5Type.Utils import ensure_list
...@@ -60,6 +61,13 @@ except NameError: # < 3.6 ...@@ -60,6 +61,13 @@ except NameError: # < 3.6
class ModuleNotFoundError(ImportError): class ModuleNotFoundError(ImportError):
pass pass
class ComponentImportError(ImportError):
"""Error when importing an existing, but invalid component, typically
because it contains syntax errors or import errors.
"""
class ComponentDynamicPackage(ModuleType): class ComponentDynamicPackage(ModuleType):
""" """
A top-level component is a package as it contains modules, this is required A top-level component is a package as it contains modules, this is required
...@@ -355,15 +363,17 @@ class ComponentDynamicPackage(ModuleType): ...@@ -355,15 +363,17 @@ class ComponentDynamicPackage(ModuleType):
# in a deadlock # in a deadlock
source_code_obj = compile(source_code_str, module.__file__, 'exec') source_code_obj = compile(source_code_str, module.__file__, 'exec')
exec(source_code_obj, module.__dict__) exec(source_code_obj, module.__dict__)
except Exception as error: except Exception:
del sys.modules[module_fullname] del sys.modules[module_fullname]
if module_fullname_alias: if module_fullname_alias:
del sys.modules[module_fullname_alias] del sys.modules[module_fullname_alias]
if module_fullname_filesystem: if module_fullname_filesystem:
del sys.modules[module_fullname_filesystem] del sys.modules[module_fullname_filesystem]
reraise(ImportError, reraise(
"%s: cannot load Component %s (%s)" % (fullname, name, error), ComponentImportError,
"%s: cannot load Component %s :\n%s" % (
fullname, name, traceback.format_exc()),
sys.exc_info()[2]) sys.exc_info()[2])
# Add the newly created module to the Version package and add it as an # Add the newly created module to the Version package and add it as an
......
...@@ -185,7 +185,7 @@ class ERP5TypeLiveTestCase(ERP5TypeTestCaseMixin): ...@@ -185,7 +185,7 @@ class ERP5TypeLiveTestCase(ERP5TypeTestCaseMixin):
finally: finally:
restoreInteraction() restoreInteraction()
from Products.ERP5Type.dynamic.component_package import ComponentDynamicPackage from Products.ERP5Type.dynamic.component_package import ComponentDynamicPackage, ComponentImportError
from Products.ERP5Type.tests.runUnitTest import ERP5TypeTestLoader from Products.ERP5Type.tests.runUnitTest import ERP5TypeTestLoader
class ERP5TypeTestReLoader(ERP5TypeTestLoader): class ERP5TypeTestReLoader(ERP5TypeTestLoader):
...@@ -221,6 +221,8 @@ class ERP5TypeTestReLoader(ERP5TypeTestLoader): ...@@ -221,6 +221,8 @@ class ERP5TypeTestReLoader(ERP5TypeTestLoader):
if module is None: if module is None:
try: try:
self._importZodbTestComponent(name.split('.')[0]) self._importZodbTestComponent(name.split('.')[0])
except ComponentImportError:
raise
except ImportError: except ImportError:
pass pass
else: else:
......
...@@ -3296,6 +3296,67 @@ class Test(ERP5TypeTestCase): ...@@ -3296,6 +3296,67 @@ class Test(ERP5TypeTestCase):
expected_msg_re = re.compile('Ran 3 test.*OK', re.DOTALL) expected_msg_re = re.compile('Ran 3 test.*OK', re.DOTALL)
self.assertRegex(output, expected_msg_re) self.assertRegex(output, expected_msg_re)
def testRunLiveTestImportError(self):
source_code = '''
def break_at_import():
import non.existing.module # pylint:disable=import-error
break_at_import()
'''
component = self._newComponent('testRunLiveTestImportError', source_code)
component.validate()
self.tic()
from Products.ERP5Type.tests.runUnitTest import ERP5TypeTestLoader
ERP5TypeTestLoader_loadTestsFromNames = ERP5TypeTestLoader.loadTestsFromNames
def loadTestsFromNames(self, *args, **kwargs):
"""
Monkey patched to simulate a reset right after importing the ZODB Test
Component whose Unit Tests are going to be executed
"""
ret = ERP5TypeTestLoader_loadTestsFromNames(self, *args, **kwargs)
from Products.ERP5.ERP5Site import getSite
getSite().portal_components.reset(force=True)
# Simulate a new REQUEST while the old one has been GC'ed
import erp5.component
erp5.component.ref_manager.clear()
import gc
gc.collect()
return ret
self.assertEqual(component.getValidationState(), 'validated')
self._component_tool.reset(force=True,
reset_portal_type_at_transaction_boundary=True)
def runLiveTest(test_name):
# ERP5TypeLiveTestCase.runLiveTest patches ERP5TypeTestCase bases, thus it
# needs to be restored after calling runLiveTest
base_tuple = ERP5TypeTestCase.__bases__
ERP5TypeTestLoader.loadTestsFromNames = loadTestsFromNames
try:
self._component_tool.runLiveTest(test_name)
finally:
ERP5TypeTestCase.__bases__ = base_tuple
ERP5TypeTestLoader.loadTestsFromNames = ERP5TypeTestLoader_loadTestsFromNames
return self._component_tool.readTestOutput()
output = runLiveTest('testRunLiveTestImportError')
self.assertIn('''
File "<portal_components/test.erp5.testRunLiveTestImportError>", line 4, in <module>
break_at_import()
File "<portal_components/test.erp5.testRunLiveTestImportError>", line 3, in break_at_import
import non.existing.module # pylint:disable=import-error
ImportError: No module named non.existing.module
''', output)
output = runLiveTest('testDoesNotExist_import_error_because_module_does_not_exist')
self.assertIn(
"ImportError: No module named testDoesNotExist_import_error_because_module_does_not_exist",
output)
def testERP5Broken(self): def testERP5Broken(self):
# Create a broken ghost object # Create a broken ghost object
import erp5.portal_type import erp5.portal_type
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment