-
Notifications
You must be signed in to change notification settings - Fork 0
Add plugin support (TOML-based, Rust-based, and Python-based) #7
Copy link
Copy link
Open
Labels
enhancementNew feature or requestNew feature or request
Description
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 mutationsImplementation 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
- Python plugins — highest value, PyO3 already available, most users write Python
- TOML enhancements — regex support, multiple replacements (low effort, high value)
- 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)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request