---
name: dstest
description: Deterministic simulation testing for containerized services. Write Lua scripts to inject chaos (pause, kill, resource deprivation) into Docker containers with reproducible, seeded fault injection. Use when writing chaos experiments, testing service resilience, or debugging distributed systems.
license: MIT
metadata:
author: bxrne
version: "0.2.0"
---
# dstest
dstest is a deterministic chaos testing framework for Docker containers. Write Lua scripts that inject faults and verify system resilience.
## Quick Start
```bash
# Run an example
# Build and install
cargo build --release
cargo install --path .
```
## Key Commands
| `cat examples/httpbin.lua \| cargo run` | Run a script via stdin |
| `dstest < script.lua` | Run script (after install) |
| `cargo test` | Run test suite |
| `cargo clippy -- -D warnings` | Lint gate (CI-enforced) |
| `cargo doc --open` | Open API docs |
## Available Faults
| `pause` | Freeze container (cgroups) |
| `kill` | Kill container (SIGKILL) |
| `deprive:disk` | Throttle disk I/O to 1MB/s |
| `deprive:network` | Disconnect from bridge network |
| `deprive:memory` | Halve memory limit (min 64MB) |
| `deprive:cpu` | Limit CPU to 20% quota |
## Configuration
Call `dstest.config()` first, then `dstest.setup()`. Full field reference:
[`src/bindings/core/README.md`](src/bindings/core/README.md).
```lua
dstest.config({
substrate = "docker", -- Required: must match the engine's compiled substrate
seed = 42, -- Required: random seed for determinism
weights = { -- Optional: fault weights (defaults below)
pause = 0.35,
kill = 0.25,
["deprive:disk"] = 0.10,
["deprive:network"] = 0.10,
["deprive:memory"] = 0.10,
["deprive:cpu"] = 0.10,
},
accumulation = "single", -- "single" (default) or "accumulate"
http_timeout = 5, -- HTTP timeout in seconds
http_retries = 30, -- HTTP retry attempts
http_retry_delay = 500, -- Delay between retries (ms)
step_delay = 1000, -- Delay before fault in single mode (ms)
})
```
## Core API
```lua
local s = dstest.setup({
image = "kennethreitz/httpbin",
ports = { 80 },
volumes = { "/absolute/host/path:/container:ro" },
env = { DEBUG = "true" },
cmd = { "python", "-m", "httpbin" },
})
-- Fault injection (namespaced under dstest.dst)
local result = dstest.dst.step() -- Single fault
local results = dstest.dst.run_steps(5) -- Multiple faults
dstest.dst.clear(s) -- Clear active faults
-- HTTP and TCP (namespaced under dstest.net)
local resp = dstest.net.http(s, "GET", "/get")
-- Container introspection (flat on dstest)
local info = dstest.inspect(s)
local logs = dstest.logs(s, { tail = "50" })
local exec_result = dstest.exec(s, {"ls", "-la", "/app"})
```
For the full API reference, see the per-module READMEs linked from
[`src/bindings/README.md`](src/bindings/README.md).
## Oracle (Automated Verification)
```lua
dstest.dst.oracle.predicate("health_check", function(subject, fault, round)
if fault == "pause" or fault == "kill" then return true end
local ok, resp = pcall(dstest.net.http, subject, "GET", "/health")
return ok and resp.status == 200
end)
local report = dstest.dst.oracle.run(function()
dstest.dst.run_steps(10)
end)
print(report.passed, report.passed_checks, report.failed_checks)
```
Full oracle reference: [`src/bindings/dst/README.md`](src/bindings/dst/README.md).
## Common Patterns
### Health Check Loop
```lua
while true do
local result = dstest.dst.step()
if not result.more then break end
if result.fault ~= "pause" and result.fault ~= "kill" then
local ok, resp = pcall(dstest.net.http, s, "GET", "/get")
if ok and resp.status == 200 then
dstest.info("healthy")
else
dstest.warn("unhealthy")
end
end
end
```
### Multi-Service Testing
```lua
local backend = dstest.setup({ image = "myapp/backend", ports = { 8080 } })
local cache = dstest.setup({ image = "redis", ports = { 6379 } })
dstest.dst.run_steps(10)
dstest.dst.clear(backend)
dstest.dst.clear(cache)
```
## Determinism
Same seed = identical fault sequence:
```lua
dstest.config({ seed = 42 })
local r1 = dstest.dst.run_steps(5)
dstest.config({ seed = 42 })
local r2 = dstest.dst.run_steps(5)
-- r1 and r2 have identical faults in identical order
```
## Logging
```lua
dstest.debug("verbose details")
dstest.info("normal progress")
dstest.warn("something concerning")
dstest.error("failure occurred")
```
## Examples
| [`examples/httpbin.lua`](examples/httpbin.lua) | HTTP analysis: GET/POST, status/body assertions, latency |
| [`examples/pg.lua`](examples/pg.lua) | PostgreSQL: connect, create table, insert, query, close |
| [`examples/oracle.lua`](examples/oracle.lua) | Fault injection with oracle predicates and invariants |
## Writing Scripts
Scripts are Lua and read from stdin. Use `pcall` for error handling since HTTP may
fail during faults:
```lua
local ok, resp = pcall(dstest.net.http, s, "GET", "/get")
if not ok then
dstest.warn("request failed: " .. tostring(resp))
end
```
## Requirements
- Docker daemon running
- Rust 1.85+ (uses 2024 edition)