Skip to content

Commit

Permalink
Merge remote-tracking branch 'clebergnu/runnable_and_suite_config'
Browse files Browse the repository at this point in the history
Signed-off-by: Cleber Rosa <[email protected]>
  • Loading branch information
clebergnu committed Aug 22, 2024
2 parents 6d70beb + 7608215 commit 6097f08
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 26 deletions.
77 changes: 57 additions & 20 deletions avocado/core/nrunner/runnable.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import base64
import collections
import copy
import json
import logging
import os
import subprocess
import sys
import warnings

import pkg_resources

Expand Down Expand Up @@ -96,13 +98,14 @@ def __init__(self, kind, uri, *args, config=None, identifier=None, **kwargs):
#: test or being the test, or an actual URI with multiple
#: parts
self.uri = uri
#: This attributes holds default configuration values that the
#: runner has determined that has interest in by setting it in
#: attr:`avocado.core.nrunner.runner.BaseRunner.CONFIGURATION_USED`
self._default_config = self.filter_runnable_config(kind, {})
#: This attributes holds configuration from Avocado proper
#: 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.filter_runnable_config(kind, {})
self.config = config or {}
self.args = args
self.tags = kwargs.pop("tags", None)
Expand Down Expand Up @@ -183,7 +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, default_config=False):
configuration_used = Runnable.get_configuration_used_by_kind(self.kind)
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):
Expand All @@ -196,22 +222,31 @@ def config(self, config):
:param config: A config dict with new values for Runnable.
:type config: dict
"""
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 Value Error. Please "
"use avocado.core.nrunner.runnable.Runnable.filter_runnable_config "
"or avocado.core.nrunner.runnable.Runnable.from_avocado_config"
)
self._config_setter_warning(config)
self._config = config

@default_config.setter
def default_config(self, config):
"""Sets the default config values based on the runnable kind.
This is not avocado config, it is a runnable config which is a subset
of avocado config based on `STANDALONE_EXECUTABLE_CONFIG_USED` which
describes essential configuration values for each runner kind.
These values are used as convenience if other values are not set
in the actual :attr:`config` itself.
:param config: A config dict with default values for this Runnable.
:type config: dict
"""
self._config_setter_warning(config, True)
self._default_config = config

@classmethod
def from_args(cls, args):
"""Returns a runnable from arguments"""
decoded_args = [_arg_decode_base64(arg) for arg in args.get("arg", ())]
return cls.from_avocado_config(
return cls(
args.get("kind"),
args.get("uri"),
*decoded_args,
Expand Down Expand Up @@ -277,7 +312,7 @@ def from_dict(cls, recipe_dict):
"""
cls._validate_recipe(recipe_dict)
config = ConfigDecoder.decode_set(recipe_dict.get("config", {}))
return cls.from_avocado_config(
return cls(
recipe_dict.get("kind"),
recipe_dict.get("uri"),
*recipe_dict.get("args", ()),
Expand All @@ -304,9 +339,11 @@ def from_avocado_config(
cls, kind, uri, *args, config=None, identifier=None, **kwargs
):
"""Creates runnable with only essential config for runner of specific kind."""
if not config:
config = {}
config = cls.filter_runnable_config(kind, config)
warnings.warn(
"from_avocado_config() is deprecated, please use the regular "
"class initialization as it has the same behavior.",
DeprecationWarning,
)
return cls(kind, uri, *args, config=config, identifier=identifier, **kwargs)

@classmethod
Expand All @@ -327,7 +364,7 @@ def get_configuration_used_by_kind(cls, kind):
if command is not None:
command = " ".join(command)
configuration_used = STANDALONE_EXECUTABLE_CONFIG_USED.get(command)
return configuration_used
return configuration_used + CONFIGURATION_USED

@classmethod
def filter_runnable_config(cls, kind, config):
Expand All @@ -349,7 +386,7 @@ def filter_runnable_config(cls, kind, config):
"""
whole_config = settings.as_dict()
filtered_config = {}
config_items = cls.get_configuration_used_by_kind(kind) + CONFIGURATION_USED
config_items = cls.get_configuration_used_by_kind(kind)
for config_item in config_items:
filtered_config[config_item] = config.get(
config_item, whole_config.get(config_item)
Expand Down
4 changes: 3 additions & 1 deletion avocado/core/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions avocado/plugins/sysinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def pre_test_runnables(self, test_runnable, suite_config=None):
return []
sysinfo_config = sysinfo.gather_collectibles_config(suite_config)
return [
Runnable.from_avocado_config(
Runnable(
"sysinfo",
"pre",
config=suite_config,
Expand All @@ -237,7 +237,7 @@ def post_test_runnables(self, test_runnable, suite_config=None):
sysinfo_config = sysinfo.gather_collectibles_config(suite_config)
return [
(
Runnable.from_avocado_config(
Runnable(
"sysinfo",
"post",
config=suite_config,
Expand All @@ -249,7 +249,7 @@ def post_test_runnables(self, test_runnable, suite_config=None):
["pass"],
),
(
Runnable.from_avocado_config(
Runnable(
"sysinfo",
"post",
config=suite_config,
Expand Down
1 change: 1 addition & 0 deletions examples/nrunner/recipes/runnable/noop_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"kind": "noop", "uri": "noop", "config": {"runner.identifier_format": "nothing-op"}}
2 changes: 1 addition & 1 deletion selftests/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"job-api-7": 1,
"nrunner-interface": 70,
"nrunner-requirement": 28,
"unit": 672,
"unit": 678,
"jobs": 11,
"functional-parallel": 307,
"functional-serial": 7,
Expand Down
2 changes: 1 addition & 1 deletion selftests/functional/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
39 changes: 39 additions & 0 deletions selftests/unit/runnable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest.mock

import avocado.core.nrunner.runnable as runnable_mod
from avocado.core.nrunner.runnable import Runnable


Expand Down Expand Up @@ -146,6 +147,44 @@ def test_config(self):
runnable.config.get("runner.identifier_format"), "{uri}-{args[0]}"
)

def test_config_at_init(self):
runnable = Runnable("exec-test", "/bin/sh")
self.assertEqual(
set(runnable.config.keys()),
set(
[
"run.keep_tmp",
"runner.exectest.exitcodes.skip",
"runner.exectest.clear_env",
"runner.identifier_format",
]
),
)

def test_config_warn_if_not_used(self):
runnable = Runnable("exec-test", "/bin/sh")
with unittest.mock.patch.object(runnable_mod.LOG, "warning") as log_mock:
runnable.config = {"some-unused-config": "foo"}
log_mock.assert_called_once()

def test_config_dont_warn_if_used(self):
with unittest.mock.patch.object(runnable_mod.LOG, "warning") as log_mock:
Runnable("noop", "noop", config={"runner.identifier_format": "noop"})
log_mock.assert_not_called()

def test_default_config(self):
runnable = Runnable("noop", "noop")
self.assertEqual(
runnable.default_config.get("runner.identifier_format"), "{uri}"
)

def test_default_and_actual_config(self):
runnable = Runnable("noop", "noop", config={"runner.identifier_format": "noop"})
self.assertEqual(runnable.config.get("runner.identifier_format"), "noop")
self.assertEqual(
runnable.default_config.get("runner.identifier_format"), "{uri}"
)

def test_identifier(self):
open_mocked = unittest.mock.mock_open(
read_data=(
Expand Down
11 changes: 11 additions & 0 deletions selftests/unit/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down

0 comments on commit 6097f08

Please sign in to comment.