diff --git a/mig/shared/conf.py b/mig/shared/conf.py index 0de37b321..20bf38fef 100644 --- a/mig/shared/conf.py +++ b/mig/shared/conf.py @@ -32,17 +32,62 @@ import os import sys +from mig.shared.configuration import Configuration from mig.shared.defaults import MIG_ENV from mig.shared.fileio import unpickle -def get_configuration_object(config_file=None, skip_log=False, +class RuntimeConfiguration: + """A proxy object to be passed in-place of a Configuration which can be + extended with information relevant only at runtime. + + Given Configuration objects are threaded into and through almost all + the necessary codepaths to make this information available, they are an + attractive place to put this - but a Configuration is currently loaded + from static per-site data. + + Resolve this dichotomy with this class - a Configuration instance will + continue to represent the static data while an object that proxies its + attributes and thus is entirely drop-in compatible with it can be handed + to callers without being mixed in with the static data. + """ + + def __init__(self, configuration): + object.__setattr__(self, '_configuration', configuration) + object.__setattr__(self, '_context', {}) + + def __delattr__(self, attr): + return delattr(self._configuration, attr) + + def __getattr__(self, attr): + return getattr(self._configuration, attr) + + def __setattr__(self, attr, value): + return setattr(self._configuration, attr, value) + + def context_get(self, context_key): + """Retrieve the context or a previously registered namespace. + """ + return self._context.get(context_key, None) + + def context_set(self, context_key, context_value): + """Attach a value as named namespace within the active configuration. + """ + self._context[context_key] = context_value + + @classmethod + def is_runtime_configuration(cls, obj): + """Is the given object a runtime configuration.""" + return isinstance(obj, cls) + + +def get_configuration_object(config_file=None, + skip_log=False, disable_auth_log=False): """Simple helper to call the general configuration init. Optional skip_log and disable_auth_log arguments are passed on to allow skipping the default log initialization and disabling auth log for unit tests. """ - from mig.shared.configuration import Configuration if config_file: _config_file = config_file elif os.environ.get('MIG_CONF', None): @@ -65,7 +110,7 @@ def get_configuration_object(config_file=None, skip_log=False, configuration = Configuration(_config_file, False, skip_log, disable_auth_log) - return configuration + return RuntimeConfiguration(configuration) def get_resource_configuration(resource_home, unique_resource_name, diff --git a/tests/support/__init__.py b/tests/support/__init__.py index eb2c8019f..c5fcd9c92 100644 --- a/tests/support/__init__.py +++ b/tests/support/__init__.py @@ -210,10 +210,12 @@ def _reset_logging(self, stream): @staticmethod def _make_configuration_instance(testcase, configuration_to_make): if configuration_to_make == 'fakeconfig': - return FakeConfiguration(logger=testcase.logger) + fake_configuration = FakeConfiguration(logger=testcase.logger) + from mig.shared.conf import RuntimeConfiguration + return RuntimeConfiguration(fake_configuration) elif configuration_to_make == 'testconfig': from mig.shared.conf import get_configuration_object - configuration = get_configuration_object(skip_log=True, + configuration = get_configuration_object(skip_log=True, disable_auth_log=True) configuration.logger = testcase.logger return configuration diff --git a/tests/test_mig_shared_conf.py b/tests/test_mig_shared_conf.py new file mode 100644 index 000000000..19eba4c85 --- /dev/null +++ b/tests/test_mig_shared_conf.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# --- BEGIN_HEADER --- +# +# test_mig_shared_configuration - unit test of configuration +# Copyright (C) 2003-2026 The MiG Project by the Science HPC Center at UCPH +# +# This file is part of MiG. +# +# MiG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# MiG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. +# +# --- END_HEADER --- +# + +"""Unit tests for shared conf""" + +import inspect +import os +import unittest + +from tests.support import MigTestCase, TEST_DATA_DIR, PY2, testmain +from tests.support.fixturesupp import FixtureAssertMixin + +from mig.shared.conf import Configuration, \ + RuntimeConfiguration, \ + get_configuration_object + + +class MigSharedConf(MigTestCase): + """Coverage of module methods.""" + + def test_get_configuration_object_returns_runtime_configuration(self): + configuration = get_configuration_object(skip_log=True, + disable_auth_log=True) + self.assertIsInstance(configuration, RuntimeConfiguration) + static_configuration = configuration._configuration + self.assertIsInstance(static_configuration, Configuration) + + +if __name__ == '__main__': + testmain() diff --git a/tests/test_support.py b/tests/test_support.py index 56b74f594..59c6cd07c 100644 --- a/tests/test_support.py +++ b/tests/test_support.py @@ -35,8 +35,7 @@ from tests.support import MigTestCase, PY2, testmain, temppath, \ AssertOver, FakeConfiguration -from mig.shared.conf import get_configuration_object -from mig.shared.configuration import Configuration +from mig.shared.conf import get_configuration_object, RuntimeConfiguration class InstrumentedAssertOver(AssertOver): @@ -143,8 +142,9 @@ def _provide_configuration(self): def test_provides_a_fake_configuration(self): configuration = self.configuration - - self.assertIsInstance(configuration, FakeConfiguration) + self.assertIsInstance(configuration, RuntimeConfiguration) + static_configuration = configuration._configuration + self.assertIsInstance(static_configuration, FakeConfiguration) def test_provides_a_fake_configuration_for_the_duration_of_the_test(self): c1 = self.configuration @@ -165,7 +165,7 @@ def test_provides_the_test_configuration(self): configuration = self.configuration # check we have a real config object - self.assertIsInstance(configuration, Configuration) + self.assertIsInstance(configuration, RuntimeConfiguration) # check for having loaded a config file from a test config dir config_file_path_parts = configuration.config_file.split(os.path.sep) config_file_path_parts.pop() # discard file part