Commit e11c60aa authored by Levin Zimmermann's avatar Levin Zimmermann

software/erp5/test: Run tests with ZEO & NEO

Before this patch all ERP5 SlapOS Integration tests only run with ZEO
storage. We should also run them against NEO, because we are using
ERP5 with NEO in SlapOS.

Because we are testing WCFS against NEO now, we need compatible
NEO => WCFS and ZODB => WCFS versions for successful tests. This is
the reason why we need to add a specific 'test.cfg' here, which
ensures compatible versions are installed.

For tests which shouldn't be split into NEO/ZEO flavoured test cases,
this patch adds the '__parameterized__' class attribute, which can
be set to 'False' (default to 'True').
parent 7168ba00
[buildout]
extends =
software.cfg
# Pick ZODB version which is compatible with both erp5/py2 + WCFS
[ZODB]
major = 4-wc2
# Currently we need to use patched NEO to run WCFS + NEO tests.
[neoppod-repository]
repository = https://lab.nexedi.com/kirr/neo.git
branch = t
...@@ -27,13 +27,14 @@ ...@@ -27,13 +27,14 @@
import json import json
import os import os
import sys
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
_setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass( _setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath( os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', 'software.cfg'))) os.path.join(os.path.dirname(__file__), '..', '..', 'test.cfg')))
setup_module_executed = False setup_module_executed = False
...@@ -45,9 +46,89 @@ def setUpModule(): ...@@ -45,9 +46,89 @@ def setUpModule():
setup_module_executed = True setup_module_executed = True
class ERP5InstanceTestCase(SlapOSInstanceTestCase): class ERP5InstanceTestMeta(type):
"""ERP5InstanceTestMeta adjusts instances of ERP5InstanceTestCase to
be run in several flavours: with ZEO and with NEO. Adjustment
of individual classes can be deactivated by setting the class
attribute '__parameterize__' to 'False'.
"""
def __new__(cls, name, bases, attrs):
base_class = super().__new__(cls, name, bases, attrs)
if base_class._isParameterized():
cls._parameterize(base_class)
return base_class
# Create two test classes from single definition: e.g. TestX -> TestX_ZEO and TestX_NEO.
@classmethod
def _parameterize(cls, base_class):
test_class_module = sys.modules[base_class.__module__].__dict__
for flavour in ("zeo", "neo"):
# Override metaclass to avoid infinite loop due to parameterized
# class which infinitely creates a parameterized class of itself.
class patched(base_class, metaclass=_deactivate):
zodb_storage = flavour
# Switch
# - .getInstanceParameterDict to ._test_getInstanceParameterDict, and
# - ._base_getInstanceParameterDict to .getInstanceParameterDict
# so that we could inject base implementation to be called above user-defined getInstanceParameterDict.
# see ERP5InstanceTestCase._base_getInstanceParameterDict for details.
patched._test_getInstanceParameterDict = patched.getInstanceParameterDict
patched.getInstanceParameterDict = patched._base_getInstanceParameterDict
name = "%s_%s" % (base_class.__name__, flavour.upper())
test_class_module[name] = type(name, (patched,), dict(patched.__dict__))
# Hide tests in patched class.
# We can't simply call 'delattr', because this wouldn't remove
# inherited tests. Overriding dir is sufficient, because this is
# the way how unittest discovers tests:
# https://github.com/python/cpython/blob/3.11/Lib/unittest/loader.py#L237
def __dir__(self):
if self._isParameterized():
return [attr for attr in super().__dir__() if not attr.startswith('test')]
return super().__dir__()
def _isParameterized(self):
return getattr(self, '__parameterize__', True)
class _deactivate(ERP5InstanceTestMeta):
"""_deactivate behaves exactly the same like plain type.
It allows the syntax
>>> class A(metaclass=ERP5InstanceTestMeta): ...
>>> class B(A, metaclass=_ERP5InstanceTestMeta): ...
to deactivate ERP5InstanceTestMeta in a subclass of A.
"""
def __new__(cls, name, bases, attrs):
return type.__new__(cls, name, bases, attrs)
def __dir__(self):
return type.__dir__(self)
class ERP5InstanceTestCase(SlapOSInstanceTestCase, metaclass=ERP5InstanceTestMeta):
"""ERP5 base test case """ERP5 base test case
""" """
# ERP5InstanceTestMeta switches:
# - _base_getInstanceParameterDict to be real getInstanceParameterDict, while
# - test-defined getInstanceParameterDict is switched to _test_getInstanceParameterDict
# here we invoke user-defined getInstanceParameterDict and adjust it according to "zodb_storage" parameter.
@classmethod
def _base_getInstanceParameterDict(cls):
try:
parameter_dict = json.loads(cls._test_getInstanceParameterDict()["_"])
except KeyError:
parameter_dict = {}
server = {"ssl": False} if cls.zodb_storage == "neo" else {}
parameter_dict["zodb"] = [{"type": cls.zodb_storage, "server": server}]
return {"_": json.dumps(parameter_dict)}
@classmethod @classmethod
def getRootPartitionConnectionParameterDict(cls): def getRootPartitionConnectionParameterDict(cls):
"""Return the output paramters from the root partition""" """Return the output paramters from the root partition"""
......
...@@ -119,6 +119,7 @@ class TestDefaultParameters(ERP5InstanceTestCase, TestPublishedURLIsReachableMix ...@@ -119,6 +119,7 @@ class TestDefaultParameters(ERP5InstanceTestCase, TestPublishedURLIsReachableMix
"""Test ERP5 can be instantiated with no parameters """Test ERP5 can be instantiated with no parameters
""" """
__partition_reference__ = 'defp' __partition_reference__ = 'defp'
__parameterize__ = False
class TestMedusa(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin): class TestMedusa(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin):
...@@ -310,6 +311,7 @@ class TestZopeNodeParameterOverride(ERP5InstanceTestCase, TestPublishedURLIsReac ...@@ -310,6 +311,7 @@ class TestZopeNodeParameterOverride(ERP5InstanceTestCase, TestPublishedURLIsReac
"""Test override zope node parameters """Test override zope node parameters
""" """
__partition_reference__ = 'override' __partition_reference__ = 'override'
__parameterize__ = False
@classmethod @classmethod
def getInstanceParameterDict(cls): def getInstanceParameterDict(cls):
......
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