# vanilla-test for Rust
[Project overview](https://github.com/RIAEvangelist/vanilla-test#readme) · [Rust documentation](https://riaevangelist.github.io/vanilla-test/rust/) · [Guide](https://riaevangelist.github.io/vanilla-test/rust/guide/) · [Examples](https://riaevangelist.github.io/vanilla-test/rust/example/) · [API](https://riaevangelist.github.io/vanilla-test/rust/api/) · [Coverage](https://riaevangelist.github.io/vanilla-test/rust/coverage/) · [docs.rs](https://docs.rs/vanilla-test) · [crates.io](https://crates.io/crates/vanilla-test) · [Live browser WASM test](https://riaevangelist.github.io/vanilla-test/rust/browser/)
[](https://crates.io/crates/vanilla-test)
[](https://crates.io/crates/vanilla-test)
[](https://doc.rust-lang.org/stable/releases.html)
[](https://riaevangelist.github.io/vanilla-test/rust/browser/)
A small, dependency-free, unsafe-free Rust implementation of the Vanilla Test lifecycle. It runs natively with Cargo and compiles the same Rust unit tests into browser WebAssembly without `wasm-bindgen`, generated glue, or a wrapper crate.
## Install
```sh
cargo add vanilla-test
```
Rust 1.85 or newer is supported. The crate has no dependencies and forbids unsafe code.
## Use
```rust
use vanilla_test::VanillaTest;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut test = VanillaTest::new();
test.expects("addition preserves the total")?;
if 1 + 2 == 3 {
test.pass()?;
} else {
test.fail()?;
}
test.done()?;
let result = test.report()?;
assert!(result.ok);
assert_eq!(result.total, 1);
Ok(())
}
```
The suite is sequential. One `VanillaTest` instance owns one active case; create separate instances for independent concurrent work.
## Lifecycle
1. `expects(description)` starts one uniquely named case.
2. `pass()` or `fail()` records the first decision.
3. `done()` closes the case; no decision means failure.
4. Repeat as needed.
5. `report()` seals the suite and returns its cached immutable result.
An empty suite passes. Exact, case-sensitive descriptions are unique within one suite. Reporting while a case is active is an error, and a reported suite cannot start another case.
## API
| Item | Contract |
| --- | --- |
| `VanillaTest::new()` | Creates an empty isolated suite. |
| `expects(&str)` | Starts one uniquely described case. |
| `pass()` | Records the first decision as passed. |
| `fail()` | Records the first decision as failed. |
| `done()` | Completes the case, defaulting an undecided case to failed. |
| `report()` | Seals the suite and returns the same borrowed `TestResult` thereafter. |
| `TestResult` | Owns passed/failed descriptions, totals, `ok`, and the plain-text report. |
| `TestError` | A typed lifecycle error that leaves the suite unchanged. |
`TestError` distinguishes `SuiteAlreadyReported`, `TestAlreadyActive`, `DuplicateDescription`, `NoActiveTest`, and `ActiveTestNotDone`.
Rust intentionally does not copy JavaScript-only runtime validators, completion events, ANSI rendering, busy-loop helpers, or process behavior. The shared value is the lifecycle; the API remains native Rust.
## Native tests
```sh
cargo fmt --check
RUSTFLAGS="-D warnings" cargo test
cargo package
```
Native Cargo is the canonical detailed diagnostic path. Focused unit tests and this README's doctest verify the native contract.
## Native source coverage
CI instruments the nine native unit tests with `rustc -C instrument-coverage`, merges their profiles once with Rust's bundled `llvm-profdata`, and publishes region, executable-line, and function results through `llvm-cov`. The artifact includes LLVM summary JSON, LCOV, and detailed source HTML without a coverage crate or Cargo plugin.
The [Rust coverage page](https://riaevangelist.github.io/vanilla-test/rust/coverage/) displays the latest successful `main` result. Browser WebAssembly stays a separate pass/fail gate because native source coverage and browser delivery answer different questions.
## Browser WebAssembly
Cargo can compile the existing `#[test]` inventory into a browser-loadable libtest harness:
```sh
rustup target add wasm32-unknown-unknown
cargo install vanilla-test --version 2.1.0
cargo vanilla-test --browser
```
`cargo vanilla-test --browser` compiles the selected package's existing library tests and emits a deployable `dist/vanilla-test/` directory containing `index.html`, `browser.js`, and `vanilla-test-tests.wasm`. Use `--out-dir PATH` to change the destination or `--manifest-path crates/name/Cargo.toml` to select a workspace member. The generated page is a working example: it verifies visible document text (including the canonical target label), result hooks, artifact response, WASM MIME type, streaming instantiation, and `main()` export, then runs the Rust harness. The same lines are written to the page and the browser console.
Use a compatible custom page when the browser site itself is part of the test:
```sh
cargo vanilla-test --browser --page tests/browser.html
```
`--page` validates and copies that file into the deployable directory as `index.html`. Start from the [built-in runner](https://github.com/RIAEvangelist/vanilla-test/blob/main/rust/browser/runner.html), keep its result hooks and `./browser.js` module, then mark any visible text that the page should verify:
```html
<h1
data-rust-browser-site-check="browser site renders its heading"
data-rust-browser-expected="Dashboard"
>Dashboard</h1>
```
Each marked element becomes one named browser check without another library or JavaScript test wrapper. Site-check failures are reported but do not suppress the Rust harness; a failed WASM delivery step skips only the browser steps that depend on it.
Under the hood, the dependency-free Cargo subcommand runs:
```sh
cargo test --release --lib --target wasm32-unknown-unknown --no-run
```
That standard Cargo command compiles the tests but does not execute them. The generated harness exports a safe `main()`; the emitted page needs only the native WebAssembly API:
```js
const { instance } = await WebAssembly.instantiateStreaming(
fetch('./vanilla-test-tests.wasm')
);
const exitCode = instance.exports.main();
if (exitCode !== 0) throw new Error(`Rust tests exited with ${exitCode}`);
```
The repository's deploy workflow resolves that generated artifact, runs it in real Chrome, verifies the visible checks, and publishes it at the [live Rust browser path](https://riaevangelist.github.io/vanilla-test/rust/browser/). That page also contains a copy-ready basic Rust suite. The Rust test logic appears once in `src/lib.rs`; native Cargo and browser WASM execute that same unit-test inventory.
The exact target name is `wasm32-unknown-unknown`. It is intentionally minimal and provides no browser DOM or console host API; standard output is inert, so the generated libtest harness exposes aggregate status rather than per-test console lines. The adapter does not copy or pretend to recover Rust test names: it reports its browser-side checks and the real Rust harness status. Native Cargo remains responsible for individual Rust test names, full libtest diagnostics, and doctests. This crate's `String`, `Vec`, `HashSet`, `Arc`, formatting, and result paths are supported; a panic traps the WASM run, and operating-system or thread-dependent tests do not belong in this browser lane. See Rust's [`wasm32-unknown-unknown` target documentation](https://doc.rust-lang.org/stable/rustc/platform-support/wasm32-unknown-unknown.html).
Deploy the output directory and any assets referenced by a custom page to the same static HTTP host. Serve `.wasm` with `application/wasm` so the browser can compile it while streaming.
## Benchmark
```sh
cargo bench --bench lifecycle -- 100000 5
```
The dependency-free harness performs one warmup, records and prints five optimized samples, times suite construction plus unique-description formatting and `expects()` → `pass()` → `done()`, then validates the sealed report outside the timed boundary. Shared results and methodology live in the [project README](https://github.com/RIAEvangelist/vanilla-test#benchmarks).
## Versions and releases
Rust follows the project's shared major/minor line while retaining language-specific patch versions. Every GitHub release contains the current package for every supported language. See the canonical [multi-language release policy](https://github.com/RIAEvangelist/vanilla-test#multi-language-releases).