Skip to content

Add plugin support (TOML-based, Rust-based, and Python-based) #7

@sakost

Description

@sakost

Summary

fest should support user-defined mutation operators through a plugin system. Three plugin types are proposed, each serving different use cases and complexity levels.

Current State

  • The `Mutator` trait (`src/mutation/mutator.rs`) is already designed for extensibility: `trait Mutator: Send + Sync` with a `find_mutations()` method
  • `MutatorRegistry` is a simple `Vec<Box>` with register/iterate
  • Config types already exist in `src/config/types.rs`:
    • `custom: Vec` — text pattern: `{ name, pattern, replacement }`
    • `python: Vec` — `{ path }`
    • `dylib: Vec` — `{ path }`
  • Custom text-pattern mutators are already implemented and wired into `build_registry()`
  • Python and dylib plugins have config parsing but are not yet wired into the registry

1. TOML-based plugins (text pattern replacement)

Status: Already implemented

[[fest.mutators.custom]]
name = "django_none_replace"
pattern = "objects.filter"
replacement = "objects.none"

Simple find-and-replace mutations defined entirely in config. Good for domain-specific mutations.

Enhancements needed

  • Support regex patterns (currently literal string match only)
  • Support multiple replacements per pattern (generate one mutant per replacement)
  • Add documentation and examples

2. Python-based plugins

Status: Config exists, not implemented 🔧

[[fest.mutators.python]]
path = "my_mutators/custom_op.py"

Design

Python plugins should define a function or class that receives source code + AST and returns mutations:

# my_mutators/custom_op.py
from fest import Mutator, Mutation, MutationContext

class DjangoQuerysetMutator(Mutator):
    name = "django_queryset"
    
    def find_mutations(self, source: str, ast: object, ctx: MutationContext) -> list[Mutation]:
        # Use Python's ast module or work with source text
        mutations = []
        # ... find and return mutations
        return mutations

Implementation approach

  • Use PyO3 to call Python plugin code from Rust
  • Pass source text and file path to the Python function
  • Receive back a list of `(byte_offset, byte_length, original_text, replacement_text)` tuples
  • fest already depends on PyO3 for the pytest plugin runner — reuse the Python interpreter

Considerations

  • Plugin discovery: explicit path in config vs. entry points vs. directory scanning
  • Error handling: plugin crashes should not kill the entire run
  • Performance: Python plugins will be slower than Rust — document this tradeoff
  • AST format: Pass Python's stdlib `ast` module tree? Or just source text + line info?

3. Rust-based plugins (dynamic libraries)

Status: Config exists, not implemented 🔧

[[fest.mutators.dylib]]
path = "target/release/libmy_mutator.so"

Design

Rust plugins compile to a shared library exposing a C-compatible interface:

use fest_plugin_api::{Mutator, Mutation, MutationContext};

pub struct MyMutator;

impl Mutator for MyMutator {
    fn name(&self) -> &str { "my_custom_op" }
    
    fn find_mutations(&self, source: &str, ctx: &MutationContext) -> Vec<Mutation> {
        // ...
    }
}

// Entry point
#[no_mangle]
pub extern "C" fn fest_create_mutator() -> Box<dyn Mutator> {
    Box::new(MyMutator)
}

Implementation approach

  • Use `libloading` crate for dynamic library loading
  • Define a stable C ABI or use `abi_stable` crate for safe Rust ABI
  • Publish a `fest-plugin-api` crate with the trait and types plugins need
  • Load at registry build time, before mutation generation

Considerations

  • ABI stability: Rust doesn't have a stable ABI. Options:
    • C ABI with opaque pointers (safest, most portable)
    • `abi_stable` crate (ergonomic but adds dependency)
    • Same-compiler-version constraint (simplest but fragile)
  • Security: Loading arbitrary shared libraries is inherently unsafe — document risks
  • Distribution: Plugins must be compiled for the same platform as fest

Suggested Implementation Order

  1. Python plugins — highest value, PyO3 already available, most users write Python
  2. TOML enhancements — regex support, multiple replacements (low effort, high value)
  3. Rust dylib plugins — for power users, needs ABI design decisions

Related

  • Mutator trait: `src/mutation/mutator.rs`
  • Registry builder: `src/mutation.rs` (`build_registry()`)
  • Config types: `src/config/types.rs`
  • cosmic-ray uses stevedore-based plugin discovery (Python entry points)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions