Extending VT Functionality Through Entry Points: Inside the Avocado VT Plugin Architecture
Avocado VT leverages Python setuptools entry points to enable runtime discovery of plugins, allowing developers to extend virtualization testing capabilities by registering classes in setup.py without modifying core framework code.
The avocado-framework/avocado-vt repository implements a modular plugin system that enables extending VT functionality through entry points defined in setup.py. This architecture allows the Avocado testing framework to discover and load additional capabilities at runtime, including custom CLI commands, test resolvers, and result handlers, using standard Python packaging metadata.
Entry Point Groups Defined in setup.py
The foundation for plugin discovery resides in the entry_points dictionary inside setup.py. This configuration maps plugin names to Python import paths under the avocado.plugins.* namespace, organizing extensions by their functional role in the testing lifecycle.
entry_points={
"console_scripts": [
"avocado-runner-avocado-vt = avocado_vt.plugins.vt_runner:main",
],
"avocado.plugins.settings": [
"vt-settings = avocado_vt.plugins.vt_settings:VTSettings",
],
"avocado.plugins.cli": [
"vt-list = avocado_vt.plugins.vt_list:VTLister",
"vt = avocado_vt.plugins.vt:VTRun",
],
"avocado.plugins.cli.cmd": [
"vt-bootstrap = avocado_vt.plugins.vt_bootstrap:VTBootstrap",
"vt-list-guests = avocado_vt.plugins.vt_list_guests:VTListGuests",
"vt-list-archs = avocado_vt.plugins.vt_list_archs:VTListArchs",
],
"avocado.plugins.result_events": [
"vt-joblock = avocado_vt.plugins.vt_joblock:VTJobLock",
"vt-cluster = avocado_vt.plugins.vt_cluster:VTCluster",
],
"avocado.plugins.init": [
"vt-init = avocado_vt.plugins.vt_init:VtInit",
],
"avocado.plugins.resolver": [
"avocado-vt = avocado_vt.plugins.vt_resolver:VTResolver"
],
"avocado.plugins.discoverer": [
"avocado-vt = avocado_vt.plugins.vt_resolver:VTDiscoverer"
],
"avocado.plugins.runnable.runner": [
"avocado-vt = avocado_vt.plugins.vt_runner:VTTestRunner",
],
}
Each entry point follows the format name = module_path:ClassName, enabling the framework to dynamically import and instantiate plugin classes.
Core Plugin Types and Their Implementations
The Avocado VT plugin architecture exposes distinct extension interfaces, each targeting a specific phase of the testing pipeline.
CLI Extensions
The avocado.plugins.cli group registers top-level subcommands, while avocado.plugins.cli.cmd registers nested subcommands under the avocado vt hierarchy. The VTLister class in avocado_vt/plugins/vt_list.py implements the list functionality, while VTRun in avocado_vt/plugins/vt.py handles the primary vt command. Subcommands like VTBootstrap in avocado_vt/plugins/vt_bootstrap.py provide utility functions such as environment bootstrapping.
Test Resolution and Discovery
Resolver plugins translate test references into runnable objects. The VTResolver class in avocado_vt/plugins/vt_resolver.py handles the avocado.plugins.resolver entry point, parsing cartesian configuration files to identify tests. The VTDiscoverer class in the same file implements the avocado.plugins.discoverer interface, listing all available VT tests without executing them. Both classes leverage the DiscoveryMixIn helper from avocado_vt/discovery.py to handle cartesian parser initialization and parameter conversion.
Test Execution
The avocado.plugins.runnable.runner group defines the execution engine for specific test types. The VTTestRunner class in avocado_vt/plugins/vt_runner.py implements this interface, handling the actual execution of virtualization tests. This module also provides the main function for the console_scripts entry point, creating the avocado-runner-avocado-vt command-line utility.
Configuration and Initialization
Settings plugins inject default configuration values into the Avocado configuration system. The VTSettings class in avocado_vt/plugins/vt_settings.py registers under avocado.plugins.settings to add VT-specific configuration sections. The initialization plugin VtInit in avocado_vt/plugins/vt_init.py executes during framework startup via the avocado.plugins.init entry point, performing early setup such as directory creation or environment validation.
Result Event Handling
Result event plugins receive notifications about test outcomes and job state changes. The VTJobLock class in avocado_vt/plugins/vt_joblock.py and VTCluster in avocado_vt/plugins/vt_cluster.py both implement the avocado.plugins.result_events interface, enabling them to implement distributed locking or cluster coordination based on test results.
Runtime Discovery Mechanism
The plugin discovery process operates through four distinct phases that transform static package metadata into active framework components.
-
Installation Phase: When installing the
avocado-vtpackage,setup.pywrites the entry-point definitions into the distribution metadata at*.dist-info/entry_points.txt. -
Bootstrap Phase: During Avocado startup, the plugin manager (
avocado.core.plugin_manager) iterates over all known groups in theavocado.plugins.*namespace, scanning installed package metadata. -
Import Phase: For each discovered entry point, the manager executes
pkg_resources.load_entry_point(or the modernimportlib.metadata.entry_points), dynamically importing the target class or function such asVTResolverorVTTestRunner. -
Registration Phase: The imported objects populate internal registries keyed by their group name. Subsequent framework stages—including argument parsing, test resolution, and result handling—query these registries to retrieve and execute the appropriate plugins.
This mechanism ensures that extending VT functionality through entry points requires no changes to the core Avocado codebase.
Practical Examples of Extending VT Functionality
The following implementations demonstrate how to leverage the entry point architecture for common extension scenarios.
Creating a Custom CLI Sub-command
To add a new top-level command, implement the CLI interface and register it under avocado.plugins.cli:
# myplugin/cli_hello.py
from avocado.core.plugin_interfaces import CLI
from avocado.core.utils import messages
class HelloWorld(CLI):
"""Simple “avocado vt‑hello” command."""
name = "vt-hello"
description = "Print a friendly greeting from a VT plug‑in."
def configure(self, parser):
# No extra options needed
pass
def run(self, config):
# The command is executed when the user runs:
# avocado vt hello
print("👋 Hello from the Avocado‑VT plug‑in!")
return messages.FinishedMessage.get("PASS")
Add the entry point in setup.py:
"avocado.plugins.cli": [
"vt-hello = myplugin.cli_hello:HelloWorld",
],
After reinstalling the package, the command appears:
$ avocado vt hello
👋 Hello from the Avocado‑VT plug‑in!
Implementing a Result Event Hook
To execute logic when tests complete, implement the ResultEvent interface:
# myplugin/result_logger.py
import os
from avocado.core.plugin_interfaces import ResultEvent
class LogResult(ResultEvent):
"""Log every test result to a custom file."""
name = "mylog"
description = "Write test outcomes to ~/vt_results.log"
def __init__(self, config):
self.log_path = os.path.expanduser("~/vt_results.log")
def post(self, test):
with open(self.log_path, "a") as f:
f.write(f"{test.id}: {test.status}\n")
# No need to return anything; Avocado continues normally.
Register the plugin:
"avocado.plugins.result_events": [
"mylog = myplugin.result_logger:LogResult",
],
When a VT test finishes, the plugin automatically appends a line to ~/vt_results.log.
Building a Custom Test Resolver
To resolve custom test references into VT runnables, implement the Resolver interface:
# myplugin/custom_resolver.py
from avocado.core.plugin_interfaces import Resolver
from avocado.core.resolver import ReferenceResolution, ReferenceResolutionResult
from avocado.core.nrunner import Runnable
class MyResolver(Resolver):
name = "my-resolver"
description = "Resolve 'my-special-test' into a VT runnable."
def resolve(self, reference):
if reference == "my-special-test":
runnable = Runnable(
"avocado-vt",
uri="my_special.cfg",
param1="value",
param2=42,
)
return ReferenceResolution(
reference, ReferenceResolutionResult.SUCCESS, [runnable]
)
return ReferenceResolution(
reference, ReferenceResolutionResult.NOTFOUND, []
)
Add the entry point:
"avocado.plugins.resolver": [
"my-resolver = myplugin.custom_resolver:MyResolver",
],
Users can now execute the custom reference:
$ avocado run my-special-test
Summary
- Entry points in
setup.pyprovide the foundation for extending VT functionality through standard Python packaging metadata. - The
avocado.plugins.*namespace organizes extensions into functional groups including CLI, resolver, runner, and result events. - Core implementations in
avocado_vt/plugins/demonstrate the pattern:VTResolverinvt_resolver.pyfor test discovery,VTTestRunnerinvt_runner.pyfor execution, andVTJobLockinvt_joblock.pyfor result handling. - Runtime discovery occurs through
importlib.metadata, importing registered classes and injecting them into Avocado's internal registries during startup. - Developers can add new capabilities by implementing standard interfaces (such as
CLIorResolver) and adding a single line to theentry_pointsconfiguration, requiring no changes to the core Avocado codebase.
Frequently Asked Questions
What is the purpose of the avocado.plugins.resolver entry point?
The avocado.plugins.resolver entry point enables extending VT functionality through entry points by allowing custom classes to translate user-provided test references into executable Runnable objects. The VTResolver class in avocado_vt/plugins/vt_resolver.py implements this interface to parse cartesian configuration files and identify virtualization tests, bridging the gap between test references and actual test execution parameters.
How does Avocado discover plugins at runtime without explicit imports?
Avocado discovers plugins by scanning installed package metadata for the avocado.plugins.* namespace using importlib.metadata.entry_points() (or the legacy pkg_resources equivalent). During the bootstrap phase, the plugin manager iterates over these entry points, dynamically imports the referenced classes such as VTTestRunner or VTDiscoverer, and registers them in internal dictionaries keyed by their functional group, making them available for CLI parsing, test resolution, and execution.
Can I extend Avocado VT with a custom test runner without modifying the core code?
Yes, you can extend the framework by implementing the avocado.core.plugin_interfaces.Runner interface and registering it under the avocado.plugins.runnable.runner entry point group. The VTTestRunner class in avocado_vt/plugins/vt_runner.py demonstrates this pattern, handling the actual execution of virtualization tests. After adding your implementation to setup.py and reinstalling the package, Avocado will automatically delegate test execution to your custom runner for the specified test type.
What is the difference between avocado.plugins.cli and avocado.plugins.cli.cmd entry points?
The avocado.plugins.cli entry point registers top-level subcommands that appear directly under the main avocado command, such as avocado vt or avocado vt-list, while avocado.plugins.cli.cmd registers subcommands that belong to the avocado vt command hierarchy, such as avocado vt-bootstrap or avocado vt-list-guests. Both are implemented by subclassing avocado.core.plugin_interfaces.CLI, but they target different levels of the command tree to organize functionality hierarchically.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →