Commit 96c6ea1c authored by Jérome Perrin's avatar Jérome Perrin

Backport and small fixes for unittest

See merge request nexedi/erp5!2051
parents 143be498 24638029
...@@ -315,6 +315,7 @@ class Test(ERP5TypeTestCase): ...@@ -315,6 +315,7 @@ class Test(ERP5TypeTestCase):
except ImportError: except ImportError:
import traceback import traceback
traceback.print_exc(file=global_stream) traceback.print_exc(file=global_stream)
traceback.print_exc() # also print on stderr
global_stream.seek(0) global_stream.seek(0)
return global_stream.read() return global_stream.read()
finally: finally:
......
...@@ -291,12 +291,18 @@ def runLiveTest(test_list, verbosity=1, stream=None, request_server_url=None, ** ...@@ -291,12 +291,18 @@ def runLiveTest(test_list, verbosity=1, stream=None, request_server_url=None, **
output = stream output = stream
if stream is None: if stream is None:
output = StringIO() output = StringIO()
def print_and_write(data): class StderrIOWrapper:
sys.stderr.write(data) def __init__(self, wrapped):
sys.stderr.flush() self._wrapped_io = wrapped
return output.write(data) def write(self, data):
print_and_write("**Running Live Test:\n") sys.stderr.write(data)
ZopeTestCase._print = print_and_write return self._wrapped_io.write(data)
def __getattr__(self, attr):
return getattr(self._wrapped_io, attr)
output = StderrIOWrapper(output)
output.write("**Running Live Test:\n")
ZopeTestCase._print = output.write
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter(kw['warnings']) warnings.simplefilter(kw['warnings'])
...@@ -306,6 +312,6 @@ def runLiveTest(test_list, verbosity=1, stream=None, request_server_url=None, ** ...@@ -306,6 +312,6 @@ def runLiveTest(test_list, verbosity=1, stream=None, request_server_url=None, **
from AccessControl.SecurityManagement import getSecurityManager, setSecurityManager from AccessControl.SecurityManagement import getSecurityManager, setSecurityManager
sm = getSecurityManager() sm = getSecurityManager()
try: try:
result = TestRunner(stream=output, verbosity=verbosity).run(suite) TestRunner(stream=output, verbosity=verbosity).run(suite)
finally: finally:
setSecurityManager(sm) setSecurityManager(sm)
...@@ -17,6 +17,7 @@ import string ...@@ -17,6 +17,7 @@ import string
import sys import sys
import time import time
import traceback import traceback
import warnings
from six.moves import configparser from six.moves import configparser
from contextlib import contextmanager from contextlib import contextmanager
from io import BytesIO from io import BytesIO
...@@ -273,6 +274,10 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase, functional.F ...@@ -273,6 +274,10 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase, functional.F
def newPassword(self): def newPassword(self):
""" Generate a password """ """ Generate a password """
forced_password = os.environ.get('insecure_erp5_test_password')
if forced_password:
warnings.warn("Using password set from environment variable")
return forced_password
return ''.join(random.SystemRandom().sample(string.ascii_letters + string.digits, 20)) return ''.join(random.SystemRandom().sample(string.ascii_letters + string.digits, 20))
def login(self, user_name=None, quiet=0): def login(self, user_name=None, quiet=0):
......
...@@ -11,6 +11,8 @@ class SetupSiteError(Exception): ...@@ -11,6 +11,8 @@ class SetupSiteError(Exception):
def patch(): def patch():
import six import six
import contextlib
import sys
import traceback import traceback
from unittest import TestCase, TextTestResult, TextTestRunner from unittest import TestCase, TextTestResult, TextTestRunner
...@@ -21,6 +23,20 @@ def patch(): ...@@ -21,6 +23,20 @@ def patch():
TestCase.assertRaisesRegex = getattr(TestCase, 'assertRaisesRegexp') TestCase.assertRaisesRegex = getattr(TestCase, 'assertRaisesRegexp')
TestCase.assertRegex = getattr(TestCase, 'assertRegexpMatches') TestCase.assertRegex = getattr(TestCase, 'assertRegexpMatches')
TestCase.assertCountEqual = TestCase.assertItemsEqual TestCase.assertCountEqual = TestCase.assertItemsEqual
@contextlib.contextmanager
def subTest(self, msg='', **params):
yield
TestCase.subTest = subTest
if sys.version_info < (3, 11):
def enterContext(self, cm):
cls = type(cm)
enter = cls.__enter__
exit = cls.__exit__
result = enter(cm)
self.addCleanup(exit, cm, None, None, None)
return result
TestCase.enterContext = enterContext
TextTestResult_addError = six.get_unbound_function(TextTestResult.addError) TextTestResult_addError = six.get_unbound_function(TextTestResult.addError)
def addError(self, test, err): def addError(self, test, err):
......
...@@ -150,6 +150,7 @@ Options: ...@@ -150,6 +150,7 @@ Options:
timer service. timer service.
This option only makes sense with --activity_node= This option only makes sense with --activity_node=
or when not specifying a test to run. or when not specifying a test to run.
--insecure_password=PWD Use `PWD` instead of generating random passwords for users.
--zserver=ADDRESS[,...] Make ZServer listen on given IPv4 address. --zserver=ADDRESS[,...] Make ZServer listen on given IPv4 address.
Addresses can be given in the following syntaxs: Addresses can be given in the following syntaxs:
- HOST:PORT - HOST:PORT
...@@ -798,7 +799,8 @@ def main(argument_list=None): ...@@ -798,7 +799,8 @@ def main(argument_list=None):
"sys_path=", "sys_path=",
"instance_home=", "instance_home=",
"log_directory=", "log_directory=",
"with_wendelin_core" "with_wendelin_core",
"insecure_password=",
]) ])
except getopt.GetoptError as msg: except getopt.GetoptError as msg:
usage(sys.stderr, msg) usage(sys.stderr, msg)
...@@ -919,6 +921,8 @@ def main(argument_list=None): ...@@ -919,6 +921,8 @@ def main(argument_list=None):
_log_directory = os.path.abspath(arg) _log_directory = os.path.abspath(arg)
elif opt == "--with_wendelin_core": elif opt == "--with_wendelin_core":
os.environ["with_wendelin_core"] = "1" os.environ["with_wendelin_core"] = "1"
elif opt == "--insecure_password":
os.environ["insecure_erp5_test_password"] = arg
setupWarnings() setupWarnings()
......
...@@ -3255,17 +3255,9 @@ class Test(ERP5TypeTestCase): ...@@ -3255,17 +3255,9 @@ class Test(ERP5TypeTestCase):
self.assertNotEqual(os, None) self.assertNotEqual(os, None)
''' '''
def testRunLiveTest(self): def _runLiveTest(self, test_name):
""" """Runs a live test from portal_components
Create a new ZODB Test Component and try to run it as a live tests and
check the expected output
""" """
# First try with a test which run successfully
source_code = self._getValidSourceCode()
component = self._newComponent('testRunLiveTest', source_code)
component.validate()
self.tic()
from Products.ERP5Type.tests.runUnitTest import ERP5TypeTestLoader from Products.ERP5Type.tests.runUnitTest import ERP5TypeTestLoader
ERP5TypeTestLoader_loadTestsFromNames = ERP5TypeTestLoader.loadTestsFromNames ERP5TypeTestLoader_loadTestsFromNames = ERP5TypeTestLoader.loadTestsFromNames
def loadTestsFromNames(self, *args, **kwargs): def loadTestsFromNames(self, *args, **kwargs):
...@@ -3287,6 +3279,35 @@ class Test(ERP5TypeTestCase): ...@@ -3287,6 +3279,35 @@ class Test(ERP5TypeTestCase):
return ret return ret
# ERP5TypeLiveTestCase.runLiveTest patches ERP5TypeTestCase bases, thus it
# needs to be restored after calling runLiveTest
base_tuple = ERP5TypeTestCase.__bases__
ERP5TypeTestLoader.loadTestsFromNames = loadTestsFromNames
from six.moves import cStringIO as StringIO
stderr = StringIO()
_real_stderr = sys.stderr
sys.stderr = stderr
try:
self._component_tool.runLiveTest(test_name)
finally:
ERP5TypeTestCase.__bases__ = base_tuple
ERP5TypeTestLoader.loadTestsFromNames = ERP5TypeTestLoader_loadTestsFromNames
sys.stderr = _real_stderr
test_output = self._component_tool.readTestOutput()
self.assertEqual(stderr.getvalue(), test_output)
return test_output
def testRunLiveTest(self):
"""
Create a new ZODB Test Component and try to run it as a live tests and
check the expected output
"""
# First try with a test which run successfully
source_code = self._getValidSourceCode()
component = self._newComponent('testRunLiveTest', source_code)
component.validate()
self.tic()
self.assertEqual(component.getValidationState(), 'validated') self.assertEqual(component.getValidationState(), 'validated')
self.assertModuleImportable('testRunLiveTest') self.assertModuleImportable('testRunLiveTest')
self._component_tool.reset(force=True, self._component_tool.reset(force=True,
...@@ -3295,19 +3316,7 @@ class Test(ERP5TypeTestCase): ...@@ -3295,19 +3316,7 @@ class Test(ERP5TypeTestCase):
# set a request key, that should not be set from the test request # set a request key, that should not be set from the test request
self.portal.REQUEST.set('foo', 'something from main request') self.portal.REQUEST.set('foo', 'something from main request')
def runLiveTest(test_name): output = self._runLiveTest('testRunLiveTest')
# 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('testRunLiveTest')
expected_msg_re = re.compile('Ran 1 test.*OK', re.DOTALL) expected_msg_re = re.compile('Ran 1 test.*OK', re.DOTALL)
self.assertRegex(output, expected_msg_re) self.assertRegex(output, expected_msg_re)
...@@ -3325,7 +3334,7 @@ class Test(ERP5TypeTestCase): ...@@ -3325,7 +3334,7 @@ class Test(ERP5TypeTestCase):
self._component_tool.reset(force=True, self._component_tool.reset(force=True,
reset_portal_type_at_transaction_boundary=True) reset_portal_type_at_transaction_boundary=True)
output = runLiveTest('testRunLiveTest') output = self._runLiveTest('testRunLiveTest')
expected_msg_re = re.compile(r'Ran 2 tests.*FAILED \(failures=1\)', re.DOTALL) expected_msg_re = re.compile(r'Ran 2 tests.*FAILED \(failures=1\)', re.DOTALL)
self.assertRegex(output, expected_msg_re) self.assertRegex(output, expected_msg_re)
...@@ -3350,7 +3359,7 @@ class Test(ERP5TypeTestCase): ...@@ -3350,7 +3359,7 @@ class Test(ERP5TypeTestCase):
self._component_tool.reset(force=True, self._component_tool.reset(force=True,
reset_portal_type_at_transaction_boundary=True) reset_portal_type_at_transaction_boundary=True)
output = runLiveTest('testRunLiveTest') output = self._runLiveTest('testRunLiveTest')
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)
...@@ -3364,44 +3373,11 @@ break_at_import() ...@@ -3364,44 +3373,11 @@ break_at_import()
component.validate() component.validate()
self.tic() 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.assertEqual(component.getValidationState(), 'validated')
self._component_tool.reset(force=True, self._component_tool.reset(force=True,
reset_portal_type_at_transaction_boundary=True) reset_portal_type_at_transaction_boundary=True)
def runLiveTest(test_name): output = self._runLiveTest('testRunLiveTestImportError')
# 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')
relative_url = 'portal_components/test.erp5.testRunLiveTestImportError' relative_url = 'portal_components/test.erp5.testRunLiveTestImportError'
if six.PY2: if six.PY2:
module_file = '<' + relative_url + '>' module_file = '<' + relative_url + '>'
...@@ -3417,7 +3393,7 @@ break_at_import() ...@@ -3417,7 +3393,7 @@ break_at_import()
%(error_message)s %(error_message)s
''' % dict(module_file=module_file, error_message=error_message), output) ''' % dict(module_file=module_file, error_message=error_message), output)
output = runLiveTest('testDoesNotExist_import_error_because_module_does_not_exist') output = self._runLiveTest('testDoesNotExist_import_error_because_module_does_not_exist')
if six.PY2: if six.PY2:
expected_output = "ImportError: No module named testDoesNotExist_import_error_because_module_does_not_exist" expected_output = "ImportError: No module named testDoesNotExist_import_error_because_module_does_not_exist"
else: else:
......
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