Commit cd2c1bc6 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.

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'.
parent b57248b5
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
import json import json
import os import os
import sys
import unittest
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
...@@ -45,9 +47,127 @@ def setUpModule(): ...@@ -45,9 +47,127 @@ 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 getattr(base_class, '__parameterize__', True):
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.
# We need to create dir_ staticially and cache it, because
# if "getTestCaseNames" is called inside __dir__ it would
# result in infinite recursion since "getTestCaseNames" calls
# 'dir(base_class)'.
test_list = unittest.TestLoader().getTestCaseNames(base_class)
dir_ = [attr for attr in dir(base_class) if attr not in test_list]
cls.id_to_dir.update({id(base_class): dir_})
# Map id of a class to its precomputed dir
id_to_dir = {}
def __dir__(self):
try:
return self.id_to_dir[id(self)]
except KeyError:
return super().__dir__()
# Because we statically cache the dir of each
# class, we also need to update the cache in case
# attributes are dynamically created or dynamically
# removed.
# XXX: If dir is already set and the user dynamically
# creates a new test via:
# test_case.test_a = ...
# this test won't be hidden.
def __setattr__(self, attr, value):
super().__setattr__(attr, value)
try:
dir_ = self.id_to_dir[id(self)]
except KeyError:
return
if attr not in dir_:
dir_.append(attr)
def __delattr__(self, attr):
super().__delattr__(attr)
try:
dir_ = self.id_to_dir[id(self)]
except KeyError:
return
if attr in dir_:
del dir_[dir_.index(attr)]
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
""" """
# 20 aren't enough for NEO
# https://lab.nexedi.com/nexedi/slapos.core/blob/1.8.4/slapos/testing/testcase.py#L273-274
# https://lab.nexedi.com/nexedi/slapos.core/blob/1.8.4/slapos/testing/testcase.py#L379
#
instance_max_retry = 100
# 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 = {}
parameter_dict["zodb"] = [{"type": cls.zodb_storage, "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