From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from gandalf.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3]) by passt.top (Postfix) with ESMTPS id 4F6675A0274 for ; Tue, 27 Jun 2023 04:54:44 +0200 (CEST) Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4Qqq7J6HTkz4wqY; Tue, 27 Jun 2023 12:54:36 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=201602; t=1687834476; bh=qVWRgoy9B8R1tki2mL96tn3rZraz/VcqMm7+EzeP+0I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QDGDoPlm0ZXtkhIoIPXbAAy+BesPtzhcNY+xjkX+ptTJ8emBc16ncgQAdQUEtsx9G xq0+OtM/KUe70a4B/OUMRkXxUmSKwR8d1qB4yPRDgapAjbdS1FLY65M3j1dAEv8D8i 22Dnb/lCS6Iyg0z4rOYqBJteJNs45kSiD/n5y1IY= From: David Gibson To: passt-dev@passt.top, Stefano Brivio Subject: [PATCH 06/27] avocado: Resolver implementation for avocado-classless plugin Date: Tue, 27 Jun 2023 12:54:07 +1000 Message-ID: <20230627025429.2209702-7-david@gibson.dropbear.id.au> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230627025429.2209702-1-david@gibson.dropbear.id.au> References: <20230627025429.2209702-1-david@gibson.dropbear.id.au> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: WLHTSJWWWZLG3RP4ASBLCWACQCJQ6CUA X-Message-ID-Hash: WLHTSJWWWZLG3RP4ASBLCWACQCJQ6CUA X-MailFrom: dgibson@gandalf.ozlabs.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: crosa@redhat.com, jarichte@redhat.com, David Gibson X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- 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 + +""" +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 + +""" +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