diff --git a/tests/unittest.py b/tests/unittest.py
index 93fee1c0e6..96ae8fca67 100644
--- a/tests/unittest.py
+++ b/tests/unittest.py
@@ -16,6 +16,7 @@
import gc
import hashlib
import hmac
+import json
import logging
import secrets
import time
@@ -53,6 +54,7 @@ from twisted.web.server import Request
from synapse import events
from synapse.api.constants import EventTypes
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
+from synapse.config._base import Config, RootConfig
from synapse.config.homeserver import HomeServerConfig
from synapse.config.server import DEFAULT_ROOM_VERSION
from synapse.crypto.event_signing import add_hashes_and_signatures
@@ -124,6 +126,63 @@ def around(target: TV) -> Callable[[Callable[Concatenate[S, P], R]], None]:
return _around
+_TConfig = TypeVar("_TConfig", Config, RootConfig)
+
+
+def deepcopy_config(config: _TConfig) -> _TConfig:
+ new_config: _TConfig
+
+ if isinstance(config, RootConfig):
+ new_config = config.__class__(config.config_files) # type: ignore[arg-type]
+ else:
+ new_config = config.__class__(config.root)
+
+ for attr_name in config.__dict__:
+ if attr_name.startswith("__") or attr_name == "root":
+ continue
+ attr = getattr(config, attr_name)
+ if isinstance(attr, Config):
+ new_attr = deepcopy_config(attr)
+ else:
+ new_attr = attr
+
+ setattr(new_config, attr_name, new_attr)
+
+ return new_config
+
+
+_make_homeserver_config_obj_cache: Dict[str, Union[RootConfig, Config]] = {}
+
+
+def make_homeserver_config_obj(config: Dict[str, Any]) -> RootConfig:
+ """Creates a :class:`HomeServerConfig` instance with the given configuration dict.
+
+ This is equivalent to::
+
+ config_obj = HomeServerConfig()
+ config_obj.parse_config_dict(config, "", "")
+
+ but it keeps a cache of `HomeServerConfig` instances and deepcopies them as needed,
+ to avoid validating the whole configuration every time.
+ """
+ cache_key = json.dumps(config)
+
+ if cache_key in _make_homeserver_config_obj_cache:
+ # Cache hit: reuse the existing instance
+ config_obj = _make_homeserver_config_obj_cache[cache_key]
+ else:
+ # Cache miss; create the actual instance
+ config_obj = HomeServerConfig()
+ config_obj.parse_config_dict(config, "", "")
+
+ # Add to the cache
+ _make_homeserver_config_obj_cache[cache_key] = config_obj
+
+ assert isinstance(config_obj, RootConfig)
+
+ return deepcopy_config(config_obj)
+
+
class TestCase(unittest.TestCase):
"""A subclass of twisted.trial's TestCase which looks for 'loglevel'
attributes on both itself and its individual test methods, to override the
@@ -528,8 +587,7 @@ class HomeserverTestCase(TestCase):
config = kwargs["config"]
# Parse the config from a config dict into a HomeServerConfig
- config_obj = HomeServerConfig()
- config_obj.parse_config_dict(config, "", "")
+ config_obj = make_homeserver_config_obj(config)
kwargs["config"] = config_obj
async def run_bg_updates() -> None:
|