From d229a5678144fc166508df6b5ef994eff1fb6be1 Mon Sep 17 00:00:00 2001 From: Jan Richter Date: Thu, 2 May 2024 11:45:26 +0200 Subject: [PATCH] Recipe with dependencies fix This commit moves dependency serialization to Dependency class from safeloader to runnable. This will enable the use of dependencies in recipe files. Reference: #5918 Signed-off-by: Jan Richter --- avocado/core/dependencies/dependency.py | 5 ---- avocado/core/nrunner/runnable.py | 22 +++++++++++++- avocado/core/safeloader/docstring.py | 6 +--- avocado/plugins/dependency.py | 17 ++++++++++- selftests/check.py | 4 +-- selftests/functional/serial/requirements.py | 33 +++++++++++++++++++++ selftests/unit/safeloader_docstring.py | 5 ++-- 7 files changed, 75 insertions(+), 17 deletions(-) diff --git a/avocado/core/dependencies/dependency.py b/avocado/core/dependencies/dependency.py index f9d2323321..4db0dce9c8 100644 --- a/avocado/core/dependencies/dependency.py +++ b/avocado/core/dependencies/dependency.py @@ -12,8 +12,6 @@ # Copyright: Red Hat Inc. 2024 # Authors: Jan Richter -from avocado.core.nrunner.runnable import Runnable - class Dependency: """ @@ -57,9 +55,6 @@ def __eq__(self, other): return hash(self) == hash(other) return False - def to_runnable(self, config): - return Runnable(self.kind, self.uri, *self.args, config=config, **self.kwargs) - @classmethod def from_dictionary(cls, dictionary): return cls( diff --git a/avocado/core/nrunner/runnable.py b/avocado/core/nrunner/runnable.py index dc2509a1c3..a818515721 100644 --- a/avocado/core/nrunner/runnable.py +++ b/avocado/core/nrunner/runnable.py @@ -15,6 +15,7 @@ except ImportError: JSONSCHEMA_AVAILABLE = False +from avocado.core.dependencies.dependency import Dependency from avocado.core.nrunner.config import ConfigDecoder, ConfigEncoder from avocado.core.settings import settings from avocado.core.utils.eggenv import get_python_path_env_if_egg @@ -100,7 +101,7 @@ def __init__(self, kind, uri, *args, config=None, **kwargs): self.config = config or {} self.args = args self.tags = kwargs.pop("tags", None) - self.dependencies = kwargs.pop("dependencies", None) + self.dependencies = self.read_dependencies(kwargs.pop("dependencies", None)) self.variant = kwargs.pop("variant", None) self.output_dir = kwargs.pop("output_dir", None) #: list of (:class:`ReferenceResolutionAssetType`, str) tuples @@ -345,6 +346,25 @@ def add_configuration_used(cls, kind, config): config[config_item] = whole_config.get(config_item) return config + def read_dependencies(self, dependencies_dict): + """ + Converts dependencies from json to avocado.core.dependencies.dependency.Dependency + + :param dependencies: Runnable dependencies + :type dependencies: list of dict, or list of Dependency + :returns: Runnable dependencies in avocado.core.dependencies.dependency.Dependency format. + :rtype: list of Dependency + """ + if isinstance(dependencies_dict, list): + return list( + map( + lambda d: ( + Dependency.from_dictionary(d) if isinstance(d, dict) else d + ), + dependencies_dict, + ) + ) + def get_command_args(self): """ Returns the command arguments that adhere to the runner interface diff --git a/avocado/core/safeloader/docstring.py b/avocado/core/safeloader/docstring.py index 421de6597d..3e819c567c 100644 --- a/avocado/core/safeloader/docstring.py +++ b/avocado/core/safeloader/docstring.py @@ -1,8 +1,6 @@ import json import re -from avocado.core.dependencies.dependency import Dependency - #: Gets the docstring directive value from a string. Used to tweak #: test behavior in various ways DOCSTRING_DIRECTIVE_RE_RAW = ( @@ -80,9 +78,7 @@ def get_docstring_directives_dependencies(docstring): if item.startswith("dependency="): _, dependency_str = item.split("dependency=", 1) try: - dependencies.append( - Dependency.from_dictionary(json.loads(dependency_str)) - ) + dependencies.append(json.loads(dependency_str)) except json.decoder.JSONDecodeError: # ignore dependencies in case of malformed dictionary continue diff --git a/avocado/plugins/dependency.py b/avocado/plugins/dependency.py index cf5b225075..44039e6002 100644 --- a/avocado/plugins/dependency.py +++ b/avocado/plugins/dependency.py @@ -15,6 +15,7 @@ from avocado.core.dependencies.dependency import Dependency from avocado.core.exceptions import JobBaseException +from avocado.core.nrunner.runnable import Runnable from avocado.core.plugin_interfaces import JobPre, PreTest @@ -43,9 +44,23 @@ def pre_test_runnables(test_runnable, suite_config=None): # pylint: disable=W02 dependency_runnables = [] unique_dependencies = list(dict.fromkeys(test_runnable.dependencies)) for dependency in unique_dependencies: - dependency_runnables.append(dependency.to_runnable(test_runnable.config)) + dependency_runnables.append( + DependencyResolver._dependency_to_runnable( + dependency, test_runnable.config + ) + ) return dependency_runnables + @staticmethod + def _dependency_to_runnable(dependency, config): + return Runnable( + dependency.kind, + dependency.uri, + *dependency.args, + config=config, + **dependency.kwargs, + ) + class SuiteDependency(JobPre): name = "suite-dependency" diff --git a/selftests/check.py b/selftests/check.py index 847f71758a..f6b24c8fe6 100755 --- a/selftests/check.py +++ b/selftests/check.py @@ -26,11 +26,11 @@ "job-api-6": 4, "job-api-7": 1, "nrunner-interface": 70, - "nrunner-requirement": 24, + "nrunner-requirement": 28, "unit": 669, "jobs": 11, "functional-parallel": 306, - "functional-serial": 6, + "functional-serial": 7, "optional-plugins": 0, "optional-plugins-golang": 2, "optional-plugins-html": 3, diff --git a/selftests/functional/serial/requirements.py b/selftests/functional/serial/requirements.py index 2fa08054d4..6f6dbfb4ea 100644 --- a/selftests/functional/serial/requirements.py +++ b/selftests/functional/serial/requirements.py @@ -130,6 +130,14 @@ def test_c(self): ] """ +DEPENDENCY_RECIPE_FMT = """ +{ + "kind": "avocado-instrumented", + "uri": "{path}", + "kwargs": {"dependencies": [{"type": "package", "name": "hello"}]} +} +""" + class BasicTest(TestCaseTmpDir, Test): """ @@ -294,3 +302,28 @@ def test_job_dependency(self): "vim-common", result.stdout_text, ) + + @skipUnless(os.getenv("CI"), skip_install_message) + def test_dependency_recipe(self): + with script.Script( + os.path.join(self.tmpdir.name, "test_multiple_success.py"), + TEST_WITHOUT_DEPENDENCY, + ) as test: + dependency_recipe_data = DEPENDENCY_RECIPE_FMT.format( + path=f"{test.path}:SuccessTest.test_a" + ) + with script.Script( + os.path.join(self.tmpdir.name, "dependency_recipe.json"), + dependency_recipe_data, + ) as recipe: + command = self.get_command(recipe.path) + result = process.run(command, ignore_status=True) + self.assertEqual(result.exit_status, exit_codes.AVOCADO_ALL_OK) + self.assertIn( + "PASS 3", + result.stdout_text, + ) + self.assertNotIn( + "vim-common", + result.stdout_text, + ) diff --git a/selftests/unit/safeloader_docstring.py b/selftests/unit/safeloader_docstring.py index 3545fcf88c..2c0e8b30ba 100644 --- a/selftests/unit/safeloader_docstring.py +++ b/selftests/unit/safeloader_docstring.py @@ -1,6 +1,5 @@ import unittest -from avocado.core.dependencies.dependency import Dependency from avocado.core.safeloader.docstring import ( DOCSTRING_DIRECTIVE_RE, check_docstring_directive, @@ -166,12 +165,12 @@ def test_get_dependency_empty(self): def test_dependency_single(self): raw = ':avocado: dependency={"foo":"bar"}' - exp = [Dependency(kwargs={"foo": "bar"})] + exp = [{"foo": "bar"}] self.assertEqual(get_docstring_directives_dependencies(raw), exp) def test_dependency_double(self): raw = ':avocado: dependency={"foo":"bar"}\n:avocado: dependency={"uri":"http://foo.bar"}' - exp = [Dependency(kwargs={"foo": "bar"}), Dependency(uri="http://foo.bar")] + exp = [{"foo": "bar"}, {"uri": "http://foo.bar"}] self.assertEqual(get_docstring_directives_dependencies(raw), exp) def test_directives_regex(self):