From efb3965ae07a89522eb1729285c54a431fcdeb44 Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Fri, 5 Apr 2024 10:22:10 -0400 Subject: [PATCH 1/7] avocado/plugins/list.py: use a more accurate name for variable The "cls" variable name is a legacy choice, based on the fact that the old runner would always be dealing with some Python classes (even for the SIMPLE tests and the like, a matching class inheriting from avocado.Test would exist). This name is confusing when reading the code and thinking about Avocado's current core concepts. Let's rename this to match the fact that we're talking about a test *kind*, the name terminology used on other places such as runnable recipe files. Signed-off-by: Cleber Rosa --- avocado/plugins/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/avocado/plugins/list.py b/avocado/plugins/list.py index bedefd2656..27ceb5e252 100644 --- a/avocado/plugins/list.py +++ b/avocado/plugins/list.py @@ -48,8 +48,8 @@ class List(CLICmd): def _prepare_matrix_for_display(matrix, verbose=False): colored_matrix = [] for item in matrix: - cls = item[0] - type_label = TERM_SUPPORT.healthy_str(cls) + kind = item[0] + type_label = TERM_SUPPORT.healthy_str(kind) if verbose: colored_matrix.append( (type_label, item[1], _get_tags_as_string(item[2] or {})) From b33fb0023c9e824259127814c2677849490f2512 Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Fri, 5 Apr 2024 10:22:10 -0400 Subject: [PATCH 2/7] avocado/plugins/list.py: simple docstring improvement Signed-off-by: Cleber Rosa --- avocado/plugins/list.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/avocado/plugins/list.py b/avocado/plugins/list.py index 27ceb5e252..cd9eebb572 100644 --- a/avocado/plugins/list.py +++ b/avocado/plugins/list.py @@ -127,7 +127,11 @@ def _display_extra(suite, verbose=True): @staticmethod def _get_resolution_matrix(suite): - """Used for resolver.""" + """Used for resolver. + + :returns: a list of tuples with either (kind, uri, tags) or + (kind, uri) depending on whether verbose mode is on + """ test_matrix = [] verbose = suite.config.get("core.verbose") for runnable in suite.tests: From e2fcc20a5bf1f88a48eed864b0969867cf5f8211 Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Fri, 5 Apr 2024 10:22:10 -0400 Subject: [PATCH 3/7] avocado/core/output.py: use fstrings instead of addition of strings First, there's a slight performance advantage in using fstrings instead of adding strings together. Second, this changes makes the output utility not enforce policy when it comes to types, that is, if the message to be printed is "None", it will be converted to a string, instead of raising TypeError. Signed-off-by: Cleber Rosa --- avocado/core/output.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/avocado/core/output.py b/avocado/core/output.py index 82f4718427..8e70de3f2e 100644 --- a/avocado/core/output.py +++ b/avocado/core/output.py @@ -105,8 +105,8 @@ def __init__(self): self.disable() elif force_color != "always": raise ValueError( - "The value for runner.output.color must be one of " - "'always', 'never', 'auto' and not " + force_color + f"The value for runner.output.color must be one of " + f"'always', 'never', 'auto' and not {force_color}" ) def disable(self): @@ -134,7 +134,7 @@ def header_str(self, msg): If the output does not support colors, just return the original string. """ - return self.HEADER + msg + self.ENDC + return f"{self.HEADER}{msg}{self.ENDC}" def fail_header_str(self, msg): """ @@ -142,7 +142,7 @@ def fail_header_str(self, msg): If the output does not support colors, just return the original string. """ - return self.FAIL + msg + self.ENDC + return f"{self.FAIL}{msg}{self.ENDC}" def warn_header_str(self, msg): """ @@ -150,7 +150,7 @@ def warn_header_str(self, msg): If the output does not support colors, just return the original string. """ - return self.WARN + msg + self.ENDC + return f"{self.WARN}{msg}{self.ENDC}" def healthy_str(self, msg): """ @@ -158,7 +158,7 @@ def healthy_str(self, msg): If the output does not support colors, just return the original string. """ - return self.PASS + msg + self.ENDC + return f"{self.PASS}{msg}{self.ENDC}" def partial_str(self, msg): """ @@ -166,7 +166,7 @@ def partial_str(self, msg): If the output does not support colors, just return the original string. """ - return self.PARTIAL + msg + self.ENDC + return f"{self.PARTIAL}{msg}{self.ENDC}" def pass_str(self, msg="PASS", move=MOVE_BACK): """ @@ -174,7 +174,7 @@ def pass_str(self, msg="PASS", move=MOVE_BACK): If the output does not support colors, just return the original string. """ - return move + self.PASS + msg + self.ENDC + return f"{move}{self.PASS}{msg}{self.ENDC}" def skip_str(self, msg="SKIP", move=MOVE_BACK): """ @@ -182,7 +182,7 @@ def skip_str(self, msg="SKIP", move=MOVE_BACK): If the output does not support colors, just return the original string. """ - return move + self.SKIP + msg + self.ENDC + return f"{move}{self.SKIP}{msg}{self.ENDC}" def fail_str(self, msg="FAIL", move=MOVE_BACK): """ @@ -190,7 +190,7 @@ def fail_str(self, msg="FAIL", move=MOVE_BACK): If the output does not support colors, just return the original string. """ - return move + self.FAIL + msg + self.ENDC + return f"{move}{self.FAIL}{msg}{self.ENDC}" def error_str(self, msg="ERROR", move=MOVE_BACK): """ @@ -198,7 +198,7 @@ def error_str(self, msg="ERROR", move=MOVE_BACK): If the output does not support colors, just return the original string. """ - return move + self.ERROR + msg + self.ENDC + return f"{move}{self.ERROR}{msg}{self.ENDC}" def interrupt_str(self, msg="INTERRUPT", move=MOVE_BACK): """ @@ -206,7 +206,7 @@ def interrupt_str(self, msg="INTERRUPT", move=MOVE_BACK): If the output does not support colors, just return the original string. """ - return move + self.INTERRUPT + msg + self.ENDC + return f"{move}{self.INTERRUPT}{msg}{self.ENDC}" def warn_str(self, msg="WARN", move=MOVE_BACK): """ @@ -214,7 +214,7 @@ def warn_str(self, msg="WARN", move=MOVE_BACK): If the output does not support colors, just return the original string. """ - return move + self.WARN + msg + self.ENDC + return f"{move}{self.WARN}{msg}{self.ENDC}" #: Transparently handles colored terminal, when one is used @@ -725,10 +725,10 @@ class Throbber: # Only print a throbber when we're on a terminal if TERM_SUPPORT.enabled: MOVES = [ - TERM_SUPPORT.MOVE_BACK + STEPS[0], - TERM_SUPPORT.MOVE_BACK + STEPS[1], - TERM_SUPPORT.MOVE_BACK + STEPS[2], - TERM_SUPPORT.MOVE_BACK + STEPS[3], + f"{TERM_SUPPORT.MOVE_BACK}{STEPS[0]}", + f"{TERM_SUPPORT.MOVE_BACK}{STEPS[1]}", + f"{TERM_SUPPORT.MOVE_BACK}{STEPS[2]}", + f"{TERM_SUPPORT.MOVE_BACK}{STEPS[3]}", ] else: MOVES = ["", "", "", ""] From 83f9b842c4ae4ec8020aa6e42e69c8a4108be6d7 Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Fri, 5 Apr 2024 10:22:10 -0400 Subject: [PATCH 4/7] Runnable recipe schema: distribute this and future schemas In order to be able to enforce better validation while loading runnable recipe schemas (and others), let's distribute the schemas. This will allow installations of Avocado with extra (optional) libraries installed (jsonschema) to be able to do schema level validation when loading those files. Signed-off-by: Cleber Rosa --- MANIFEST.in | 1 + .../schemas/runnable-recipe.schema.json | 0 python-avocado.spec | 11 ++++++++++- selftests/unit/nrunner_schema.py | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) rename {contrib => avocado}/schemas/runnable-recipe.schema.json (100%) diff --git a/MANIFEST.in b/MANIFEST.in index b865c85e8d..e3d723ae48 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,5 +4,6 @@ include README.rst include VERSION recursive-include avocado/etc * recursive-include avocado/libexec * +recursive-include avocado/schemas * recursive-include selftests * recursive-include examples * diff --git a/contrib/schemas/runnable-recipe.schema.json b/avocado/schemas/runnable-recipe.schema.json similarity index 100% rename from contrib/schemas/runnable-recipe.schema.json rename to avocado/schemas/runnable-recipe.schema.json diff --git a/python-avocado.spec b/python-avocado.spec index 29994921f4..d8eabfd694 100644 --- a/python-avocado.spec +++ b/python-avocado.spec @@ -28,7 +28,7 @@ Summary: Framework with tools and libraries for Automated Testing Name: python-avocado Version: 104.0 -Release: 1%{?gitrel}%{?dist} +Release: 2%{?gitrel}%{?dist} License: GPLv2+ and GPLv2 and MIT URL: https://avocado-framework.github.io/ %if 0%{?rel_build} @@ -191,6 +191,8 @@ cp -r examples/tests %{buildroot}%{_docdir}/avocado cp -r examples/yaml_to_mux %{buildroot}%{_docdir}/avocado cp -r examples/varianter_pict %{buildroot}%{_docdir}/avocado cp -r examples/varianter_cit %{buildroot}%{_docdir}/avocado +mkdir -p %{buildroot}%{_datarootdir}/avocado +mv %{buildroot}%{python3_sitelib}/avocado/schemas %{buildroot}%{_datarootdir}/avocado find %{buildroot}%{_docdir}/avocado -type f -name '*.py' -exec chmod -c -x {} ';' mkdir -p %{buildroot}%{_libexecdir}/avocado mv %{buildroot}%{python3_sitelib}/avocado/libexec/* %{buildroot}%{_libexecdir}/avocado @@ -263,6 +265,10 @@ Common files (such as configuration) for the Avocado Testing Framework. %dir %{_sysconfdir}/avocado/scripts/job/pre.d %dir %{_sysconfdir}/avocado/scripts/job/post.d %dir %{_sharedstatedir}/avocado +%dir %{_sharedstatedir}/avocado/data +%dir %{_datarootdir}/avocado +%dir %{_datarootdir}/avocado/schemas +%{_datarootdir}/avocado/schemas/* %config(noreplace)%{_sysconfdir}/avocado/sysinfo/commands %config(noreplace)%{_sysconfdir}/avocado/sysinfo/files %config(noreplace)%{_sysconfdir}/avocado/sysinfo/profilers @@ -436,6 +442,9 @@ Again Shell code (and possibly other similar shells). %{_libexecdir}/avocado* %changelog +* Tue Apr 2 2024 Cleber Rosa - 104.0-2 +- Package JSON schema files + * Tue Mar 19 2024 Jan Richter - 104.0-1 - New release diff --git a/selftests/unit/nrunner_schema.py b/selftests/unit/nrunner_schema.py index 924924fc46..2c162ad8b5 100644 --- a/selftests/unit/nrunner_schema.py +++ b/selftests/unit/nrunner_schema.py @@ -13,7 +13,7 @@ JSONSCHEMA_AVAILABLE = False -BASE_SCHEMA_DIR = os.path.join(BASEDIR, "contrib", "schemas") +BASE_SCHEMA_DIR = os.path.join(BASEDIR, "avocado", "schemas") BASE_RECIPE_DIR = os.path.join(BASEDIR, "examples", "nrunner", "recipes") From 62908a57014a8c9d4b8d05bd2121fdcbe1da98bb Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Fri, 5 Apr 2024 10:22:10 -0400 Subject: [PATCH 5/7] python-avocado.spec: remove empty libexec dir When using Avocado from packages, the libexec files will be under /usr/libexec. There's no need to keep the empty libexec dir under the Python lib dir. Signed-off-by: Cleber Rosa --- python-avocado.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python-avocado.spec b/python-avocado.spec index d8eabfd694..4d6b87a0c9 100644 --- a/python-avocado.spec +++ b/python-avocado.spec @@ -196,6 +196,7 @@ mv %{buildroot}%{python3_sitelib}/avocado/schemas %{buildroot}%{_datarootdir}/av find %{buildroot}%{_docdir}/avocado -type f -name '*.py' -exec chmod -c -x {} ';' mkdir -p %{buildroot}%{_libexecdir}/avocado mv %{buildroot}%{python3_sitelib}/avocado/libexec/* %{buildroot}%{_libexecdir}/avocado +rmdir %{buildroot}%{python3_sitelib}/avocado/libexec %if %{with tests} %check @@ -444,6 +445,7 @@ Again Shell code (and possibly other similar shells). %changelog * Tue Apr 2 2024 Cleber Rosa - 104.0-2 - Package JSON schema files +- Removed empty libexec dir * Tue Mar 19 2024 Jan Richter - 104.0-1 - New release From d79d5b62b2c8c8f19b131d7e6d35161e2dedc4e5 Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Fri, 5 Apr 2024 10:22:10 -0400 Subject: [PATCH 6/7] Implement runtime validation of runnable recipes Even though there's already checks for the example runnable recipe files we ship, many bugs and weird behaviors can arise if we feed broken or missing data to the resolvers, runners, etc. This attempts to prevent such situations when the runnable recipe come from an external source, let's say, when a user is writing a recipe by hand. Because we don't want to increase the dependencies on the Avocado package, quite the contrary, we attempt to use the "jsonschema" package when available, but, if it's not, then we perform a weaker but still valid check. For people using Avocado from RPM packages, because there are already some dependencies being pulled, jsonschema becomes an additional dependency. Signed-off-by: Cleber Rosa --- avocado/core/nrunner/runnable.py | 64 ++++++++++++++++++++++++++++++++ python-avocado.spec | 3 ++ 2 files changed, 67 insertions(+) diff --git a/avocado/core/nrunner/runnable.py b/avocado/core/nrunner/runnable.py index 7b49ec937a..2f10c2f4a7 100644 --- a/avocado/core/nrunner/runnable.py +++ b/avocado/core/nrunner/runnable.py @@ -2,11 +2,19 @@ import collections import json import logging +import os import subprocess import sys import pkg_resources +try: + import jsonschema + + JSONSCHEMA_AVAILABLE = True +except ImportError: + JSONSCHEMA_AVAILABLE = False + 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 @@ -20,6 +28,14 @@ #: The configuration that is known to be used by standalone runners STANDALONE_EXECUTABLE_CONFIG_USED = {} +#: Location used for schemas when packaged (as in RPMs) +SYSTEM_WIDE_SCHEMA_PATH = "/usr/share/avocado/schemas" + + +class RunnableRecipeInvalidError(Exception): + """Signals that a runnable recipe is not well formed, contains + missing or bad data""" + def _arg_decode_base64(arg): """ @@ -196,6 +212,53 @@ def from_args(cls, args): **_key_val_args_to_kwargs(args.get("kwargs", [])), ) + @staticmethod + def _validate_recipe_json_schema(recipe): + """Attempts to validate the runnable recipe using a JSON schema + + :param recipe: the recipe already parsed from JSON into a dict + :type recipe: dict + :returns: whether the runnable recipe JSON was attempted to be + validated with a JSON schema + :rtype: bool + :raises: RunnableRecipeInvalidError if the recipe is invalid + """ + if not JSONSCHEMA_AVAILABLE: + return False + schema_filename = "runnable-recipe.schema.json" + schema_path = pkg_resources.resource_filename( + "avocado", os.path.join("schemas", schema_filename) + ) + if not os.path.exists(schema_path): + schema_path = os.path.join(SYSTEM_WIDE_SCHEMA_PATH, schema_filename) + if not os.path.exists(schema_path): + return False + with open(schema_path, "r", encoding="utf-8") as schema: + try: + jsonschema.validate(recipe, json.load(schema)) + except jsonschema.exceptions.ValidationError as details: + raise RunnableRecipeInvalidError(details) + return True + + @classmethod + def _validate_recipe(cls, recipe): + """Validates a recipe using either JSON schema or builtin logic + + :param recipe: the recipe already parsed from JSON into a dict + :type recipe: dict + :returns: None + :raises: RunnableRecipeInvalidError if the recipe is invalid + """ + if not cls._validate_recipe_json_schema(recipe): + # This is a simplified validation of the recipe + allowed = set(["kind", "uri", "args", "kwargs", "config"]) + if not "kind" in recipe: + raise RunnableRecipeInvalidError('Missing required property "kind"') + if not set(recipe.keys()).issubset(allowed): + raise RunnableRecipeInvalidError( + "Additional properties are not allowed" + ) + @classmethod def from_recipe(cls, recipe_path): """ @@ -207,6 +270,7 @@ def from_recipe(cls, recipe_path): """ with open(recipe_path, encoding="utf-8") as recipe_file: recipe = json.load(recipe_file) + cls._validate_recipe(recipe) config = ConfigDecoder.decode_set(recipe.get("config", {})) return cls.from_avocado_config( recipe.get("kind"), diff --git a/python-avocado.spec b/python-avocado.spec index 4d6b87a0c9..85aa4adf6d 100644 --- a/python-avocado.spec +++ b/python-avocado.spec @@ -84,6 +84,7 @@ Requires: python3-avocado-common == %{version}-%{release} Requires: gdb Requires: gdb-gdbserver Requires: procps-ng +Requires: python3-jsonschema %if ! 0%{?rhel} Requires: python3-pycdlib %endif @@ -446,6 +447,8 @@ Again Shell code (and possibly other similar shells). * Tue Apr 2 2024 Cleber Rosa - 104.0-2 - Package JSON schema files - Removed empty libexec dir +- Require python3-jsonschema to perform runtime schema validation + for recipe files * Tue Mar 19 2024 Jan Richter - 104.0-1 - New release From 319eb5a7e39c9d82688d71e8b14add44cceebfec Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Fri, 5 Apr 2024 10:22:10 -0400 Subject: [PATCH 7/7] Introduce "Runnable Recipe" resolver Avocado uses a Runnable to describe what is to be executed (usually a test) in a given Task. Runnables (and Tasks) can be defined in JSON files, which are called Recipes (see the examples/nrunner/recipes directory). These can be loaded and executed with a command such as: avocado-runner-exec-test runnable-run-recipe \ examples/nrunner/recipes/runnables/exec_test_echo_no_newline.json It may also be useful to run them with an Avocado job. To do that, a "runnable-recipe" resolver is being introduced, which can read from these JSON files and create runtime runnables (which will be added to a job, a suite and turned into a runtime task). Signed-off-by: Cleber Rosa --- avocado/plugins/resolvers.py | 17 ++++++ .../source/guides/writer/chapters/recipes.rst | 59 +++++++++++++++++++ docs/source/guides/writer/index.rst | 1 + selftests/check.py | 2 +- selftests/functional/resolver.py | 16 +++++ setup.py | 1 + 6 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 docs/source/guides/writer/chapters/recipes.rst diff --git a/avocado/plugins/resolvers.py b/avocado/plugins/resolvers.py index 3020751a22..3057b80a88 100644 --- a/avocado/plugins/resolvers.py +++ b/avocado/plugins/resolvers.py @@ -143,3 +143,20 @@ def resolve(self, reference): return ReferenceResolution( reference, ReferenceResolutionResult.SUCCESS, [runnable] ) + + +class RunnableRecipeResolver(Resolver): + name = "runnable-recipe" + description = "Test resolver for JSON runnable recipes" + + def resolve(self, reference): + criteria_check = check_file( + reference, reference, suffix=".json", type_name="JSON file" + ) + if criteria_check is not True: + return criteria_check + + runnable = Runnable.from_recipe(reference) + return ReferenceResolution( + reference, ReferenceResolutionResult.SUCCESS, [runnable] + ) diff --git a/docs/source/guides/writer/chapters/recipes.rst b/docs/source/guides/writer/chapters/recipes.rst new file mode 100644 index 0000000000..48f99af61d --- /dev/null +++ b/docs/source/guides/writer/chapters/recipes.rst @@ -0,0 +1,59 @@ +Defining what to run using recipe files +--------------------------------------- + +If you've followed the previous documentation sections, you should now +be able to write ``exec-test`` tests and also ``avocado-instrumented`` +tests. These tests should be found when you run +``avocado run /reference/to/a/test``. Internally, though, these will +be defined as a :class:`avocado.core.nrunner.runnable.Runnable`. + +This is interesting because you are able to have a shortcut into what +Avocado runs by defining a ``Runnable``. Runnables can be defined using +pure Python code, such as in the following Job example: + +.. literalinclude:: ../../../../../examples/jobs/custom_exec_test.py + +But, they can also be defined in JSON files, which we call "runnable +recipes", such as: + +.. literalinclude:: ../../../../../examples/nrunner/recipes/runnables/exec_test_sleep_3.json + + +Runnable recipe format +~~~~~~~~~~~~~~~~~~~~~~ + +While it should be somewhat easy to see the similarities between +between the fields in the +:class:`avocado.core.nrunner.runnable.Runnable` structure and a +runnable recipe JSON data, Avocado actually ships with a schema that +defines the exact format of the runnable recipe: + +.. literalinclude:: ../../../../../avocado/schemas/runnable-recipe.schema.json + +Avocado will attempt to enforce the JSON schema any time a +``Runnable`` is loaded from such recipe files. + +Using runnable recipes as references +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Avocado ships with a ``runnable-recipe`` resolver plugin, which means +that you can use runnable recipe file as a reference, and get +something that Avocado can run (that is, a ``Runnable``). Example:: + + avocado list examples/nrunner/recipes/runnables/python_unittest.json + python-unittest selftests/unit/test.py:TestClassTestUnit.test_long_name + +And just as runnable recipe's resolution can be listed, they can also +be executed:: + + avocado run examples/nrunner/recipes/runnables/python_unittest.json + JOB ID : bca087e0e5f16e62f24430602f87df67ecf093f7 + JOB LOG : ~/avocado/job-results/job-2024-04-17T11.53-bca087e/job.log + (1/1) selftests/unit/test.py:TestClassTestUnit.test_long_name: STARTED + (1/1) selftests/unit/test.py:TestClassTestUnit.test_long_name: PASS (0.02 s) + RESULTS : PASS 1 | ERROR 0 | FAIL 0 | SKIP 0 | WARN 0 | INTERRUPT 0 | CANCEL 0 + JOB TIME : 2.72 s + +.. tip:: As a possible integration strategy with existing tests, you + can have one or more runnable recipe files that are passed + to Avocado to be executed. diff --git a/docs/source/guides/writer/index.rst b/docs/source/guides/writer/index.rst index 201580e1f5..b745f0da3e 100644 --- a/docs/source/guides/writer/index.rst +++ b/docs/source/guides/writer/index.rst @@ -7,6 +7,7 @@ Avocado Test Writer's Guide chapters/basics chapters/writing + chapters/recipes chapters/logging chapters/parameters chapters/libs diff --git a/selftests/check.py b/selftests/check.py index c4d7ac4dfc..5454114dc5 100755 --- a/selftests/check.py +++ b/selftests/check.py @@ -29,7 +29,7 @@ "nrunner-requirement": 16, "unit": 669, "jobs": 11, - "functional-parallel": 301, + "functional-parallel": 302, "functional-serial": 4, "optional-plugins": 0, "optional-plugins-golang": 2, diff --git a/selftests/functional/resolver.py b/selftests/functional/resolver.py index a226670ce1..42e7e8ea49 100644 --- a/selftests/functional/resolver.py +++ b/selftests/functional/resolver.py @@ -123,6 +123,22 @@ def test_corrupted_reference(self): result.stderr_text, ) + def test_runnable_recipe(self): + test_path = os.path.join( + BASEDIR, + "examples", + "nrunner", + "recipes", + "runnables", + "exec_test_echo_no_newline.json", + ) + cmd_line = f"{AVOCADO} list {test_path}" + result = process.run(cmd_line) + self.assertEqual( + b"exec-test /bin/echo\n", + result.stdout, + ) + if __name__ == "__main__": unittest.main() diff --git a/setup.py b/setup.py index b47e329d12..80dc1e1e62 100755 --- a/setup.py +++ b/setup.py @@ -451,6 +451,7 @@ def run(self): "python-unittest = avocado.plugins.resolvers:PythonUnittestResolver", "avocado-instrumented = avocado.plugins.resolvers:AvocadoInstrumentedResolver", "tap = avocado.plugins.resolvers:TapResolver", + "runnable-recipe = avocado.plugins.resolvers:RunnableRecipeResolver", ], "avocado.plugins.suite.runner": [ "nrunner = avocado.plugins.runner_nrunner:Runner",