A field guide for writing tests against the CodeTwin codebase. Read this before adding a new test so the layout stays coherent.
Rust has three first-class test locations. We use all of them for different reasons.
| Location | What it tests | How it’s run |
|---|---|---|
tests/ (this folder) |
Integration tests — public API only | cargo test --test <name> |
src/**/*.rs (inline) |
Unit tests — private functions, fast feedback | cargo test --lib |
examples/ |
Executable examples — compiled, human-facing | cargo run --example <name> |
Doc comments (///) |
Doctests — usage snippets in public docs | cargo test --doc |
benches/ (future) |
Benchmarks — perf regressions (criterion) |
cargo bench |
tests/. They import codetwin as an external crate and use only
pub items. One file per concern (e.g. tests/ir.rs, tests/pipeline.rs) — no mod wrappers.#[cfg(test)] mod tests { ... } block
at the bottom of the file. They can reach into private items.tests/fixtures/<name>/ — treat them as read-only inputs, never mutate in
place; copy to a tempfile::TempDir first.pub fn/pub struct
whose usage is not obvious.tests/ vs. inlineDoes the test only need pub items?
├── yes → tests/<concern>.rs (integration)
└── no → src/<module>.rs `mod tests` (unit)
Does the test need a filesystem fixture?
├── yes → tests/<concern>.rs (integration)
└── no → either is fine
test_ prefixes (being in tests/ is signal enough).ir.rs, discovery.rs, pipeline.rs over test_ir.rs, ir_tests.rs.cargo test --all # everything
cargo test --lib # unit tests only
cargo test --test ir # single integration file
cargo test -- --nocapture # print stdout/stderr
cargo test -- --include-ignored # run #[ignore]-gated tests
cargo test ir::serde # filter by path substring
Tests that depend on the filesystem or external tooling should be marked #[ignore] with a
comment explaining why; CI runs them with --include-ignored.
use tempfile::TempDir;
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"x\"\nversion = \"0.1.0\"\n").unwrap();
The TempDir is cleaned up automatically on drop.
use pretty_assertions::assert_eq;
assert_eq!(actual, expected);
pretty_assertions is a dev-dependency — see Cargo.toml.
let model = CodeModel::new("rust");
let json = serde_json::to_string(&model).unwrap();
let parsed: CodeModel = serde_json::from_str(&json).unwrap();
assert_eq!(model, parsed);
let output = render(&model, &config).unwrap();
assert!(output.contains("## Modules"));
assert!(output.contains("```mermaid"));
When snapshot drift becomes painful, reach for insta and wire it via cargo-insta.
#[ignore] gate for slow/fs tests#[test]
#[ignore = "touches the real filesystem; run with --include-ignored"]
fn watches_real_directory() { /* ... */ }
Use when: the behaviour is well-specified, the design is still fuzzy, or you’re fixing a bug.
tests/<concern>.rs if the API is public; otherwise inside a mod tests { } block.cargo test --test <concern> — it must fail for the reason you expect.Use when: you’re exploring a design, the shape is unclear, or you’re integrating an external tool and need to feel out the boundary.
tests/ — one per user-visible behaviour. These lock the
public API so you can refactor freely.pub fn whose usage is not obvious from the signature.Both workflows end in the same place: a feature with comprehensive, fast tests. The difference is when you write them. Non-TDD only wins when you genuinely don’t know the design yet — don’t use it to dodge test-writing.
| File | What it covers |
|---|---|
tests/ir.rs |
IR serde round-trips, CodeModel::merge semantics |
tests/config.rs |
codetwin.toml parsing, defaults, missing-file fallback |
tests/drivers.rs |
DriverRegistry detection + lookup |
tests/layouts.rs |
Layout registry + MVP layout smoke tests |
tests/pipeline.rs |
Discovery + end-to-end gen in a TempDir |
tests/snapshot.rs |
SnapshotStore round-trip |
tests/diff.rs |
diff::diff on empty + simple CodeModels |
tests/cli.rs |
CLI argument parsing (no subprocess) |
tests/fixtures/ |
Read-only sample projects consumed by other test files |
tests/<concern>.rs.use codetwin::...; — only pub items are reachable.#[test] functions over one big one — failure messages become self-
documenting.pub mod anything under tests/. Shared helpers live in tests/common/mod.rs (create
when needed).