The current resolver for the avocado-classless plugin requires you to give a full function name for every test you want to run. Implement a more "normal" resolver which will run all suitable tests matching a regular expression. In the test files, tests are marked by using a decorator to put them in a special "manifest" of tests in the file. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- test/Makefile | 4 ++ .../avocado_classless/manifest.py | 43 +++++++++++++++++++ .../avocado_classless/plugin.py | 35 +++++++++++---- .../avocado_classless/test.py | 18 ++++++++ test/avocado_classless/examples.py | 8 +++- test/avocado_classless/selftests.py | 22 ++++++++++ 6 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 test/avocado_classless/avocado_classless/manifest.py create mode 100644 test/avocado_classless/avocado_classless/test.py create mode 100644 test/avocado_classless/selftests.py diff --git a/test/Makefile b/test/Makefile index 00b84856..fc8d3a6f 100644 --- a/test/Makefile +++ b/test/Makefile @@ -231,6 +231,10 @@ avocado-assets: avocado: avocado-assets $(VENV) $(AVOCADO) run avocado +.PHONY: avocado-meta +avocado-meta: avocado-assets $(VENV) + $(AVOCADO) run $(PLUGIN)/selftests.py + flake8: $(VENV) $(VENV)/bin/flake8 $(PYPKGS) diff --git a/test/avocado_classless/avocado_classless/manifest.py b/test/avocado_classless/avocado_classless/manifest.py new file mode 100644 index 00000000..8f7fe688 --- /dev/null +++ b/test/avocado_classless/avocado_classless/manifest.py @@ -0,0 +1,43 @@ +#! /usr/bin/python3 + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright Red Hat +# Author: David Gibson <david(a)gibson.dropbear.id.au> + +""" +Manage the manifest of classless tests in a module +""" + +import sys + +MANIFEST = "__avocado_classless__" + + +def manifest(mod): + """Return avocado-classless manifest for a Python module""" + if not hasattr(mod, MANIFEST): + setattr(mod, MANIFEST, {}) + return getattr(mod, MANIFEST) + + +class ManifestTestInfo: # pylint: disable=R0903 + """Metadata about a classless test""" + + def __init__(self, func): + self.func = func + + def run_test(self): + self.func() + + +def manifest_add(mod, name, func): + """Register a function as a classless test""" + + if isinstance(mod, str): + mod = sys.modules[mod] + + mfest = manifest(mod) + if name in mfest: + raise ValueError(f"Duplicate classless test name {name}") + mfest[name] = ManifestTestInfo(func) diff --git a/test/avocado_classless/avocado_classless/plugin.py b/test/avocado_classless/avocado_classless/plugin.py index 48b89ce9..442642d5 100644 --- a/test/avocado_classless/avocado_classless/plugin.py +++ b/test/avocado_classless/avocado_classless/plugin.py @@ -12,10 +12,12 @@ Implementation of the Avocado resolver and runner for classless tests. import importlib import multiprocessing import os.path +import re import sys import time import traceback + from avocado.core.extension_manager import PluginPriority from avocado.core.test import Test, TestID from avocado.core.nrunner.app import BaseRunnerApp @@ -33,6 +35,7 @@ from avocado.core.resolver import ( ) from avocado.core.utils import messages +from .manifest import manifest SHBANG = b'#! /usr/bin/env avocado-runner-avocado-classless' DEFAULT_TIMEOUT = 5.0 @@ -60,7 +63,10 @@ class ClasslessResolver(Resolver): priority = PluginPriority.HIGH def resolve(self, reference): - path, _ = reference.rsplit(':', 1) + if ':' in reference: + path, pattern = reference.rsplit(':', 1) + else: + path, pattern = reference, '' # First check it looks like a Python file filecheck = check_file(path, reference) @@ -77,25 +83,31 @@ class ClasslessResolver(Resolver): info=f'{path} does not have first line "{SHBANG}" line', ) + mod = load_mod(path) + mfest = manifest(mod) + + pattern = re.compile(pattern) + runnables = [] + for name in mfest.keys(): + if pattern.search(name): + runnables.append(Runnable("avocado-classless", + f"{path}:{name}")) + return ReferenceResolution( reference, ReferenceResolutionResult.SUCCESS, - [Runnable("avocado-classless", reference)] + runnables, ) -def run_classless(runnable, queue): +def run_classless(runnable, testinfo, queue): """Invoked within isolating process, run classless tests""" try: - path, testname = runnable.uri.rsplit(':', 1) - mod = load_mod(path) - test = getattr(mod, testname) - class ClasslessTest(Test): """Shim class for classless tests""" def test(self): """Execute classless test""" - test() + testinfo.run_test() result_dir = runnable.output_dir instance = ClasslessTest( @@ -159,10 +171,15 @@ class ClasslessRunner(BaseRunner): def run(self, runnable): yield messages.StartedMessage.get() + try: + path, testname = runnable.uri.rsplit(':', 1) + mod = load_mod(path) + testinfo = manifest(mod)[testname] + queue = multiprocessing.SimpleQueue() process = multiprocessing.Process( - target=run_classless, args=(runnable, queue) + target=run_classless, args=(runnable, testinfo, queue) ) process.start() diff --git a/test/avocado_classless/avocado_classless/test.py b/test/avocado_classless/avocado_classless/test.py new file mode 100644 index 00000000..eb2caef2 --- /dev/null +++ b/test/avocado_classless/avocado_classless/test.py @@ -0,0 +1,18 @@ +#! /usr/bin/env python3 + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright Red Hat +# Author: David Gibson <david(a)gibson.dropbear.id.au> + +""" +Test writer facing interface to avocodo-classless +""" + +from .manifest import manifest_add + + +def test(func): + """Function decorator to mark a function as a classless test""" + manifest_add(func.__module__, func.__name__, func) + return func diff --git a/test/avocado_classless/examples.py b/test/avocado_classless/examples.py index 3895ee81..a4856124 100644 --- a/test/avocado_classless/examples.py +++ b/test/avocado_classless/examples.py @@ -1,16 +1,22 @@ #! /usr/bin/env avocado-runner-avocado-classless """ -Example avocado-classless style tests +Example avocado-classless style tests. Note that some of these +are expected to fail. + """ import sys +from avocado_classless.test import test + +@test def trivial_pass(): print("Passes, trivially") +@test def trivial_fail(): print("Fails, trivially", file=sys.stderr) assert False diff --git a/test/avocado_classless/selftests.py b/test/avocado_classless/selftests.py new file mode 100644 index 00000000..26d02378 --- /dev/null +++ b/test/avocado_classless/selftests.py @@ -0,0 +1,22 @@ +#! /usr/bin/env avocado-runner-avocado-classless + +""" +Self tests for avocado-classless plugins +""" + +from avocado_classless.manifest import manifest_add +from avocado_classless.test import test + + +@test +def trivial(): + pass + + +@test +def assert_true(): + assert True + + +# Check re-registering a function under a different name +manifest_add(__name__, "trivial2", trivial) -- 2.41.0