From 7608215109c7baaefe51f9872af473b48fc32067 Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Thu, 22 Aug 2024 01:35:19 -0400 Subject: [PATCH] Runnable and Suite configuration support This change allows for the a runnable configuration and the suite and default configuration to coexist with the correct behavior. In short, if the suite has a configuration, it will become the new default configuration for a runnable, while its own configuration will not be touched. Because the purpose of the default_config and config are different, the checks for the values they may contain are different. The default_config should contain the exact keys as in the used config. The config itself, on the other hand, may contain nothing, or some values, but it should never be more than a subset of the config. Note: there's an opportunity here for changing the data structure used for the "config" attribute, one that looks up the value in the "default_config" if it doesn't exist in the actual "config". But, given that the size of the "default_config" will always be very small (given that it's filtered to contain only the used configuration by the runner) it seemed an unnecessary optimization at this time. Fixes: https://github.com/avocado-framework/avocado/issues/5998 Signed-off-by: Cleber Rosa --- avocado/core/nrunner/runnable.py | 35 +++++++++++-------- avocado/core/suite.py | 4 ++- .../nrunner/recipes/runnable/noop_config.json | 1 + selftests/check.py | 2 +- selftests/functional/resolver.py | 2 +- selftests/unit/suite.py | 11 ++++++ 6 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 examples/nrunner/recipes/runnable/noop_config.json diff --git a/avocado/core/nrunner/runnable.py b/avocado/core/nrunner/runnable.py index 800675e78d..7669f5835b 100644 --- a/avocado/core/nrunner/runnable.py +++ b/avocado/core/nrunner/runnable.py @@ -1,5 +1,6 @@ import base64 import collections +import copy import json import logging import os @@ -105,10 +106,7 @@ def __init__(self, kind, uri, *args, config=None, identifier=None, **kwargs): #: that is passed to runners, as long as a runner declares #: its interest in using them with #: attr:`avocado.core.nrunner.runner.BaseRunner.CONFIGURATION_USED` - self._config = {} - if config is None: - config = {} - self.config = self.filter_runnable_config(kind, config) + self.config = config or {} self.args = args self.tags = kwargs.pop("tags", None) self.dependencies = self.read_dependencies(kwargs.pop("dependencies", None)) @@ -188,21 +186,30 @@ def identifier(self): @property def config(self): - return self._config + if not self._config: + return self._default_config + config_with_defaults = copy.copy(self._default_config) + config_with_defaults.update(self._config) + return config_with_defaults @property def default_config(self): return self._default_config - def _config_setter_warning(self, config): + def _config_setter_warning(self, config, default_config=False): configuration_used = Runnable.get_configuration_used_by_kind(self.kind) - if not set(configuration_used).issubset(set(config.keys())): - LOG.warning( - "The runnable config should have only values " - "essential for its runner. In the next version of " - "avocado, this will raise a ValueError. Please " - "use avocado.core.nrunner.runnable.Runnable.filter_runnable_config" - ) + if default_config: + if set(configuration_used) == (set(config.keys())): + return + else: + if set(config.keys()).issubset(set(configuration_used)): + return + LOG.warning( + "The runnable config should have only values " + "essential for its runner. In the next version of " + "avocado, this will raise a ValueError. Please " + "use avocado.core.nrunner.runnable.Runnable.filter_runnable_config" + ) @config.setter def config(self, config): @@ -232,7 +239,7 @@ def default_config(self, config): :param config: A config dict with default values for this Runnable. :type config: dict """ - self._config_setter_warning(config) + self._config_setter_warning(config, True) self._default_config = config @classmethod diff --git a/avocado/core/suite.py b/avocado/core/suite.py index 88949f526d..286c532406 100644 --- a/avocado/core/suite.py +++ b/avocado/core/suite.py @@ -76,7 +76,9 @@ def resolutions_to_runnables(resolutions, config): if resolution.result != ReferenceResolutionResult.SUCCESS: continue for runnable in resolution.resolutions: - runnable.config = runnable.filter_runnable_config(runnable.kind, config) + runnable.default_config = runnable.filter_runnable_config( + runnable.kind, config + ) result.append(runnable) return result diff --git a/examples/nrunner/recipes/runnable/noop_config.json b/examples/nrunner/recipes/runnable/noop_config.json new file mode 100644 index 0000000000..009060233a --- /dev/null +++ b/examples/nrunner/recipes/runnable/noop_config.json @@ -0,0 +1 @@ +{"kind": "noop", "uri": "noop", "config": {"runner.identifier_format": "nothing-op"}} diff --git a/selftests/check.py b/selftests/check.py index e88b0ac898..ea81932dfa 100755 --- a/selftests/check.py +++ b/selftests/check.py @@ -27,7 +27,7 @@ "job-api-7": 1, "nrunner-interface": 70, "nrunner-requirement": 28, - "unit": 677, + "unit": 678, "jobs": 11, "functional-parallel": 307, "functional-serial": 7, diff --git a/selftests/functional/resolver.py b/selftests/functional/resolver.py index 6b0eea5ee8..f9b64449db 100644 --- a/selftests/functional/resolver.py +++ b/selftests/functional/resolver.py @@ -183,7 +183,7 @@ def test_runnables_recipe(self): ================== asset: 1 exec-test: 3 -noop: 2 +noop: 3 package: 1 python-unittest: 1 sysinfo: 1""" diff --git a/selftests/unit/suite.py b/selftests/unit/suite.py index 51c967e8b9..af91128439 100644 --- a/selftests/unit/suite.py +++ b/selftests/unit/suite.py @@ -85,6 +85,17 @@ def test_config_runnable(self): runnable = suite.tests[0] self.assertEqual(runnable.config.get("runner.identifier_format"), "NOT FOO") + def test_config_runnable_and_suite(self): + config = { + "resolver.references": [ + "examples/nrunner/recipes/runnable/noop_config.json", + ], + "runner.identifier_format": "NOT FOO", + } + suite = TestSuite.from_config(config) + runnable = suite.tests[0] + self.assertEqual(runnable.config.get("runner.identifier_format"), "nothing-op") + def tearDown(self): self.tmpdir.cleanup()