# Extending VT Functionality Through Entry Points: Inside the Avocado VT Plugin Architecture

> Discover how Avocado VT plugins extend virtualization testing using Python setuptools entry points. Register custom classes in setup.py to enhance functionality without altering core code.

- Repository: [avocado/avocado-vt](https://github.com/avocado-framework/avocado-vt)
- Tags: internals
- Published: 2026-02-25

---

**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`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/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.

```python
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`](https://github.com/avocado-framework/avocado-vt/blob/main/avocado_vt/plugins/vt_list.py) implements the list functionality, while `VTRun` in [`avocado_vt/plugins/vt.py`](https://github.com/avocado-framework/avocado-vt/blob/main/avocado_vt/plugins/vt.py) handles the primary `vt` command. Subcommands like `VTBootstrap` in [`avocado_vt/plugins/vt_bootstrap.py`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/avocado_vt/plugins/vt_joblock.py) and `VTCluster` in [`avocado_vt/plugins/vt_cluster.py`](https://github.com/avocado-framework/avocado-vt/blob/main/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.

1. **Installation Phase**: When installing the `avocado-vt` package, [`setup.py`](https://github.com/avocado-framework/avocado-vt/blob/main/setup.py) writes the entry-point definitions into the distribution metadata at `*.dist-info/entry_points.txt`.

2. **Bootstrap Phase**: During Avocado startup, the plugin manager (`avocado.core.plugin_manager`) iterates over all known groups in the `avocado.plugins.*` namespace, scanning installed package metadata.

3. **Import Phase**: For each discovered entry point, the manager executes `pkg_resources.load_entry_point` (or the modern `importlib.metadata.entry_points`), dynamically importing the target class or function such as `VTResolver` or `VTTestRunner`.

4. **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`:

```python

# 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`](https://github.com/avocado-framework/avocado-vt/blob/main/setup.py):

```python
"avocado.plugins.cli": [
    "vt-hello = myplugin.cli_hello:HelloWorld",
],

```

After reinstalling the package, the command appears:

```bash
$ 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:

```python

# 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:

```python
"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:

```python

# 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:

```python
"avocado.plugins.resolver": [
    "my-resolver = myplugin.custom_resolver:MyResolver",
],

```

Users can now execute the custom reference:

```bash
$ avocado run my-special-test

```

## Summary

- **Entry points** in [`setup.py`](https://github.com/avocado-framework/avocado-vt/blob/main/setup.py) provide 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: `VTResolver` in [`vt_resolver.py`](https://github.com/avocado-framework/avocado-vt/blob/main/vt_resolver.py) for test discovery, `VTTestRunner` in [`vt_runner.py`](https://github.com/avocado-framework/avocado-vt/blob/main/vt_runner.py) for execution, and `VTJobLock` in [`vt_joblock.py`](https://github.com/avocado-framework/avocado-vt/blob/main/vt_joblock.py) for 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 `CLI` or `Resolver`) and adding a single line to the `entry_points` configuration, 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`](https://github.com/avocado-framework/avocado-vt/blob/main/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`](https://github.com/avocado-framework/avocado-vt/blob/main/avocado_vt/plugins/vt_runner.py) demonstrates this pattern, handling the actual execution of virtualization tests. After adding your implementation to [`setup.py`](https://github.com/avocado-framework/avocado-vt/blob/main/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.