---
title: 'FANDANGO-RS: Rust Evolutionary Testing'
url: https://www.emergentmind.com/topics/fandango-rs
type: topic
---

# FANDANGO-RS: Rust Evolutionary Testing

FANDANGO-RS (“Fast And Dependable ANd GeneratOr in Rust for Grammar‐based Evolutionary Testing”) is a high-performance, Rust-based re-engineering of evolutionary language-based testing (ELBT) that compiles context-free grammars (CFGs) and semantic constraints into optimized code and applies advanced multi-objective evolutionary algorithms to efficiently generate diverse, valid, and semantically constrained test inputs. Designed to radically improve upon prior art in terms of speed, scalability, and constraint-solving capability, FANDANGO-RS demonstrates performance improvements of three to four orders of magnitude over previous implementations, reducing test input generation from hours to seconds and solving constraint systems previously inaccessible to earlier approaches [2511.05987].

## 1. System Architecture and Design

FANDANGO-RS parses a context-free grammar \( G = (N, \Sigma, P, S) \) into a directed graph, where nodes correspond to grammar primitives (nonterminals, concatenations, alternations, Kleene-stars, terminals). Each production \( A \rightarrow \alpha_1 \ldots \alpha_k \) is represented as a subgraph, merged on shared nonterminal nodes. 

Cyclic dependencies in the grammar are handled by computing a minimal feedback-arc set \( F \), breaking those edges and introducing explicit indirections (Box\<T\> or Vec\<T\>), thereby ensuring acyclicity in the type structure and avoiding infinitely sized Rust types.

The overall pipeline consists of:
1. **CFG to graph transformation:** \( \text{CFG} \rightarrow G' \)
2. **Feedback-arc set computation:** Find minimal \( F \subseteq \text{edges}(G') \) such that \( G'\setminus F \) is acyclic.
3. **Type emission:** For each node \( v \) in \( G'\setminus F \):
   - Concatenation: `struct V { c₀: Child₀, ..., cₖ₋₁: Childₖ₋₁ }`
   - Alternation: `enum V { Case₀(Child₀), ..., Caseₖ₋₁(Childₖ₋₁) }`
   - Kleene-star: `Vec<Child>`
   - Terminal: zero-sized type with `const DATA: &'static [u8]`
4. **Edge wrapping:** For each cut edge \( (u \rightarrow v) \in F \), wrap \( v \) in `Box<...>` or `Vec<...>`

This ≈1 KLoC transpiler generates `.rs` files encapsulating the grammar’s structure. Rust’s monomorphization ensures that tree traversal, mutation, and constraint-checking compile into zero-overhead, pointer-free loops.

### Samplers and Generators

Random derivation-tree construction is performed by samplers associated with each nonterminal node, which decide alternation arms and Kleene-star counts. The system supports extensible generators—user-customizable plugins for structural bias (e.g., ensuring uniform alternation arm selection)—implemented as generic Rust code that benefits from compiler inlining and specialization.

### Visitor Pattern

All tree operations (pretty-printing, constraint-checking, crossover, mutation) rely on a generic `Visitor<T>` trait over opaque grammar node types. Due to Rust’s monomorphization, these visitor traversals are compiled to optimal, low-overhead routines.

#### Example Visit Method (typical structure)
```rust
fn visit<'a, N: Node<Type<'a>=T>>(&mut self, node: &'a N, _idx: usize)
    -> ControlFlow<Self::Break, Self::Continue> {
    match node.definition() {
        FandangoNode::Terminal(s) => { /* handle s */ }
        _ => node.opaque().visit_each(self) // recurse
    }
}
```
N is a compile-time constant, removing all runtime matches and indirection.

## 2. Evolutionary Algorithm Enhancements

FANDANGO-RS supports two core evolutionary search modes:
- **Single-objective genetic algorithm (GA):** As in the original FANDANGO, using a weighted-sum fitness function.
- **Multi-objective NSGA-II:** Employs true multi-objective search, dispensing with weighted-sum bias in favor of Pareto front formation.

Key genetic operators are:
- **Mutation:** Select a random path \( p \) in tree \( T \) of size \( n \), replace it with a fresh subtree \( U \) of size \( m \); runtime \( O(n + m) \).
- **Crossover:** Select paths \( p_1 \) in \( T_1 \) (size \( n_1 \)), \( p_2 \) in \( T_2 \) (size \( n_2 \)), swap subtrees of matching types and sizes \( m_1, m_2 \); runtime \( O(n_1 + n_2) \), implemented as contiguous data byte-swaps in Rust.
- **Selection:** NSGA-II uses fast non-dominated sorting (\( O(N^2) \) worst case, typically much faster at \( N \approx 100 \)), enabling effective multi-objective optimization and diversity control (e.g. k-path coverage).

Representative per-operation performance for the CSV grammar:
- Generation: \( T_{\text{generate}}(n) = 7.95\,n\,\mathrm{ns} \)
- Constraint check: \( T_{\text{check}}(n) = 21.61\,n\,\mathrm{ns} \)
- Mutation: \( T_{\text{mutate}}(n, m) \approx 0.17\,n + 8.47\,m\,\mathrm{ns} \)
- Crossover: \( T_{\text{crossover}}(n_1, n_2, m_1, m_2) \approx 1.23\,n_1 + 19.08\,n_2 - 2.44\,m_1 - 3.77\,m_2\,\mathrm{ns} \)

In the multi-objective setting, each constraint becomes an independent objective \( f_i \), and next-generation individuals are chosen via Pareto front order, with optional niching or user-supplied diversity metrics.

## 3. Constraint Solving Strategy

Semantic constraints are implemented as Rust visitor functions, each returning a real-valued “distance to satisfaction.” For constraints \( c_1,\ldots, c_m \) and candidate tree \( T \), the fitness vector is \( f(T) = (f_1(T), ..., f_m(T)) \), where \( f_i(T) \geq 0 \) and \( f_i(T)=0 \iff T \) satisfies \( c_i \).

For instance, enforcing “all CSV rows have the same number of fields” uses two passes: field counts are collected per row, and the fitness is \( |count(r_1)-count(r_2)| \). The evolutionary loop is as follows:

1. Initialize population \( P^0 \)
2. For \( g = 1 \ldots G \):
   - Evaluate \( f(x) \) for all \( x \in P^{g-1} \)
   - Sort into Pareto fronts
   - Select parents, apply crossover & mutation to create \( Q \)
   - Survivor selection: \( P^g = elitist\_survivor(P^{g-1} \cup Q) \)
3. Terminate as per stopping criterion

Due to monomorphized, statically-shaped visitors, constraint evaluation per tree is \( O(n) \), with constant factors in the tens of nanoseconds per node.

## 4. Empirical Evaluation and Comparative Results

FANDANGO-RS was evaluated against Python FANDANGO on grammars including CSV, REST, ScriptSizeC, and XML. Empirically, Rust-based generate/check/mutate/crossover operations achieve 3–4 orders of magnitude speedup over Python (nanoseconds vs microseconds per node). Static typing and monomorphization confer an additional ≈3× gain over dynamic Rust implementations.

Table: Generate Time per Node

| Grammar        | Python FANDANGO (µs/node) | Rust FANDANGO-RS (ns/node) |
|----------------|--------------------------|----------------------------|
| CSV            | 28.56 n                  | 7.95 n                     |
| ScriptSizeC    | 39.52 n                  | 12.06 n                    |

End-to-end, Python FANDANGO requires minutes to hours for non-trivial constraint satisfaction, whereas FANDANGO-RS operates in seconds.

## 5. Case Study: Test Generation for a C Subset

A C-subset grammar (omitting pointers, arrays, and conditionals) and nine semantic constraints were used to benchmark FANDANGO-RS:

- Variables declared before use
- No empty struct definitions
- No out-of-bounds field access
- No void types except in function returns
- Non-void functions must return
- No duplicate struct-field names
- No redeclarations in the same scope
- No reserved-keyword identifiers
- Type-correct expressions

Two modes were tested under a 60 s budget:
A. Validity constraints only  
B. Validity & size/generation constraints (≥5 statements, ≤1 struct, ≤1 function)

Results (mean of 5 trials):

| Mode                   | # valid progs | k-path (k=5) | gen time | gcc time | total  |
|------------------------|---------------|--------------|----------|----------|--------|
| Unconstrained          | 626/5220      | 74.3         | 0.02 s   | 60.3 s   | 60.3 s |
| Validity only          | 1977/1978     | 77.4         | 1.94 s   | 58.8 s   | 60.8 s |
| Validity + size/goals  | 401/401       | 64.9         | 55.6 s   | 12.3 s   | 67.9 s |

Python FANDANGO failed to generate any valid samples under tight size constraints in the same timeframe. k-path coverage was computed by aggregating all unique grammar paths of depth 5 present among generated programs.

Example generated C snippet:
```
void wE(double e, char R) {
    int G; bool SdF6 = 15; !true; !true; return;
}
float j2;
```

## 6. Core Innovations and Future Perspectives

FANDANGO-RS distinguishes itself over previous ELBT systems by:
- Compiling grammars to Rust types and visitor traits, achieving zero-cost, monomorphized execution for generation, mutation, crossover, and constraint checking.
- Utilizing NSGA-II for true multi-objective optimization, supporting custom diversity metrics such as k-path coverage.
- Providing extensible samplers/generators, enabling precise user control without loss of performance.

Proposed future directions include:
- Parameterized testing for additional languages by leveraging the reusable visitor infrastructure.
- Integrating coverage-guided generation via LibAFL to more effectively target unexplored code regions.
- Developing hybrid symbolic-evolutionary constraint solving for domains where arithmetic or unsatisfiable constraints are prevalent.
- Applying further code-level optimizations such as equality-saturation for constraint simplification.
- Introducing DSLs or grammar-inference tooling to lower the usability barrier for non-Rust users.

By systematically uniting Rust's static optimization capabilities with advanced multi-objective evolutionary algorithms, FANDANGO-RS enables expressive, constraint-driven, grammar-based test generation at performance levels previously unattainable, supporting practical application to complex input domains such as compiler testing [2511.05987].

Source: https://www.emergentmind.com/topics/fandango-rs