https://github.com/stacks-network/madhouse-rs
Model-based Rust state machine testing. Inspired by Hughes, Testing the Hard Stuff and Staying Sane.
https://github.com/stacks-network/madhouse-rs
Last synced: 13 days ago
JSON representation
Model-based Rust state machine testing. Inspired by Hughes, Testing the Hard Stuff and Staying Sane.
- Host: GitHub
- URL: https://github.com/stacks-network/madhouse-rs
- Owner: stacks-network
- Created: 2025-02-05T17:34:39.000Z (5 months ago)
- Default Branch: master
- Last Pushed: 2025-05-12T14:25:52.000Z (about 2 months ago)
- Last Synced: 2025-06-12T18:37:25.475Z (17 days ago)
- Language: Rust
- Homepage:
- Size: 139 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# madhouse-rs
Model-based Rust state machine testing.
## Overview
Tests state machines via sequences of command objects. Each command:
1. Checks preconditions via check()
2. Mutates state via apply()
3. Verifies assertions### Command flow
```
+-------+
| State |
+-------+
^
|
+---------+ +----+----+ +-----------+
| Command | --> | check() | --> | apply() |
+---------+ +---------+ | [asserts] |
^ +-----------+
|
+----------+
| Strategy |
+----------+
```## Usage
```rust
use madhouse::*;
use proptest::prelude::*;
use std::env;
use std::sync::Arc;// State + Context
#[derive(Debug, Default)]
struct Counter {
value: u32,
max: u32,
}
impl State for Counter {}#[derive(Debug, Clone, Default)]
struct Ctx {}
impl TestContext for Ctx {}// Commands
struct Inc {
amount: u32,
}
impl Command for Inc {
fn check(&self, s: &Counter) -> bool {
s.value + self.amount <= s.max
}
fn apply(&self, s: &mut Counter) {
s.value += self.amount;
}
fn label(&self) -> String {
format!("INC({})", self.amount)
}
fn build(_: Arc) -> impl Strategy> {
(1..=5u32).prop_map(|n| CommandWrapper::new(Inc { amount: n }))
}
}struct Reset;
impl Command for Reset {
fn check(&self, s: &Counter) -> bool {
s.value > 0
}
fn apply(&self, s: &mut Counter) {
s.value = 0;
}
fn label(&self) -> String {
"RESET".to_string()
}
fn build(_: Arc) -> impl Strategy> {
Just(CommandWrapper::new(Reset))
}
}fn main() {
let ctx = Arc::new(Ctx::default());
scenario![ctx, Inc, Reset, (Inc { amount: 42 })];
}
```## Testing Modes
- **Normal**: Commands run in specified order but proptest strategies will generate different values across runs unless using a fixed seed
- **Random**: Commands chosen pseudorandomly (set `MADHOUSE=1`)
- **Shrinking**: To shrink test cases, set `PROPTEST_MAX_SHRINK_ITERS`## Example
Run tests:
```bash
# Normal mode
cargo test# Random mode
MADHOUSE=1 cargo test# With shrinking
MADHOUSE=1 PROPTEST_MAX_SHRINK_ITERS=100 cargo test
```## Features
- Trait-based command design
- Self-validating commands
- Timing information
- Test case shrinking## License
GPL-3.0
Copyright (C) 2025 Stacks Open Internet Foundation.