# Testing

URL: /docs/tour/testing
Markdown: /docs/tour/testing.md

> Unit tests, property tests, contract-generated tests, snapshots, and deterministic handlers in Sakura Lang.

> **Pre-alpha**
>
> The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates.

Tests are part of the language. `test` and `property` are top-level items, so they live next to the code they test, use the same handlers as production code, and run with `sakura test`. Assertions come from `std/test`, whose names are in the prelude ([design §10.6](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)).

## Unit tests [#unit-tests]

A `test` has a name and a block. It behaves like a function `fn() -> Unit ! {Test, Fail<E>}` for any error union `E`. The `Test` effect provides the assertions; an assertion that does not hold records a failure and lets the test continue, and the runner fails the test at the end.

```sakura
fn greet(name: String) -> String { "Hello, {name}" }

test "greets by name" {
  expect_eq(greet("Ada"), "Hello, Ada")
}
```

| Assertion                       | Passes when                                                           |
| ------------------------------- | --------------------------------------------------------------------- |
| `expect(cond)`                  | `cond` is `true`.                                                     |
| `expect_eq(actual, expected)`   | The values are equal. Both must be `Eq` and `Show`.                   |
| `expect_fail(fn() { … }, err)`  | The body fails with exactly `err`.                                    |
| `expect_fails_with(fn() { … }, pred)` | The body fails with an error for which `pred` returns `true`.   |
| `expect_defect(fn() { … })`     | The body defects. It runs in a child task.                            |
| `expect_output(lines)`          | The test's captured `Console` and `Log` output so far is exactly `lines`. |
| `expect_snapshot(name, value)`  | `value` renders the same as the stored snapshot.                      |

## Testing typed errors and defects [#testing-typed-errors-and-defects]

Expected failures are values in the error union, so tests can compare them directly. `try` without `catch` turns a failing call into a `Result`:

```sakura
error ParseError(input: String)

fn parse_age(s: String) -> Int ! {Fail<ParseError>} {
  match String.parse_int(s) {
    Some(n) if n >= 0 => n,
    _ => fail ParseError { input: s },
  }
}

test "bad age is a typed failure" {
  let r = try { parse_age("x") }
  expect_eq(r, Err(ParseError { input: "x" }))
}

test "same check with expect_fail" {
  expect_fail(fn() { parse_age("x") }, ParseError { input: "x" })
}

test "a typed failure that escapes fails the test" {
  let age = parse_age("41")    // a ParseError here fails the test with its Show
  expect_eq(age, 41)
}
```

Because a test's row includes `Fail<E>`, a test can call failing functions directly, and postfix `?` works on a `Result` as it does anywhere else. A failure that escapes the body fails the test and reports `failed with <Show of the error>`, for example `failed with ParseError { input: "x" }`. `expect_fail` remains the way to assert that a failure happens.

> **Typed failures in tests**
>
> A typed failure may escape a test body. The runner reports it as a failed test; `V3020` is for other unhandled effects. Use `expect_fail` when the failure is the expected result.

[Contract](/docs/tour/contracts) violations are defects, not failures, so `try` cannot catch them. Use `expect_defect`:

```sakura
test "precondition is a caller defect" {
  expect_defect(fn() { clamp(1, 5, 0) })
}
```

## Tests swap handlers [#tests-swap-handlers]

Tests run under deterministic default handlers ([design §5.8](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)):

| Effect    | Default handler in tests          |
| --------- | --------------------------------- |
| `Clock`   | `Clock.fixed(Instant.from_unix_millis(0))` |
| `Random`  | `Random.seeded(seed)`, seed from `--seed` |
| `Env`     | `Env.of(Map.empty())`             |
| `Console` | `Console.captured()`              |
| `Log`     | `Log.captured()`                  |

`Console.captured()` and `Log.captured()` send each line to the test's output, which `Test.output_lines()` returns and `expect_output` checks. A `Log.captured()` line looks like `Warn message key=value`:

```sakura
fn announce(name: String) -> Unit ! {Console} {
  Console.print("hello, {name}")
}

test "prints a greeting" {
  announce("Sakura Lang")
  expect_output(["hello, Sakura Lang"])
}
```

Any other effect the test body performs must be handled inside the test with `with`. Otherwise the compiler reports `V3020` (for example, "test uses Sql; install a handler with `with`"). A unit test cannot reach a real database or network by accident; the runner also denies network access.

No mocking framework is needed, because an effect is already an interface. Here a user-defined effect gets a stateful in-memory handler:

```sakura
effect Counter {
  fn next() -> Int
  fn reset() -> Unit
}

fn memory_counter(start: Int) -> Handler<Counter> {
  handler Counter {
    var n: Int = start
    fn next() { n = n + 1; n }
    fn reset() { n = start }
  }
}

fn label_all(items: List<String>) -> List<String> ! {Counter} {
  List.map(items, fn(item) { "{Counter.next()}. {item}" })
}

test "labels count from the handler's start" {
  let out = with memory_counter(0) { label_all(["a", "b"]) }
  expect_eq(out, ["1. a", "2. b"])
}

test "inner handler shadows outer" {
  let out = with memory_counter(0) {
    with memory_counter(100) { Counter.next() }
  }
  expect_eq(out, 101)
}
```

The standard library ships fakes for its own effects:

| Fake                          | Replaces                                              |
| ----------------------------- | ----------------------------------------------------- |
| `Sql.memory()`                | A database.                                           |
| `Http.scripted(script)`       | The HTTP client, answering from a list of request matchers and responses. |
| `Fs.memory(files)`            | The filesystem, from a list of `(path, bytes)` pairs. |
| `Llm.scripted(responses)`     | A model provider. `Llm.replay(path)` replays a recording. |
| `Clock.virtual()`             | The clock. Time advances only when told to.           |
| `Task.deterministic(seed)`    | The task scheduler. Interleavings are reproducible.   |

See [Handlers](/docs/tour/handlers) for how `with` and handler state work.

## Deterministic simulation [#deterministic-simulation]

Every source of nondeterminism in Sakura Lang is an effect: time, randomness, scheduling, network. Installing deterministic handlers for all of them makes a concurrent test reproducible from its seeds. There is no separate simulator; `with` is the mechanism ([design §8.7](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)).

```sakura
test "deterministic under virtual time" {
  with Http.scripted([(Any, Response.status(200, "x"))]), Clock.virtual(), Random.seeded(1), Task.deterministic(1) {
    expect_eq(fetch_both(Url.from("https://a"), Url.from("https://b")), ("x", "x"))
  }
}
```

`fetch_both` spawns two tasks, retries with backoff, and wraps everything in a timeout ([design §15.10](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). Under these handlers, sleeps and timeouts use virtual time and task interleavings follow the seed. See [Concurrency](/docs/tour/concurrency).

## Property tests [#property-tests]

A `property` takes typed parameters. The runner generates values for them and runs the body many times. Parameter types must be written out.

```sakura
fn normalize(xs: List<Int>) -> List<Int> { List.unique(List.sort(xs)) }

property "normalize is idempotent"(xs: List<Int>) {
  expect_eq(normalize(normalize(xs)), normalize(xs))
}

property "normalize preserves membership"(xs: List<Int>, x: Int)
  requires List.contains(xs, x)
{ expect(List.contains(normalize(xs), x)) }
```

A `requires` clause on a property restricts the generated inputs. Like every contract, it must be in the [contract subset](/docs/tour/contracts#the-contract-subset). `property` parameters always use generators derived from their types. When that is not enough, write a `test` that drives explicit generators from `std/gen`:

```sakura
import std/gen

test "normalize output is strictly ascending" {
  gen.for_all(gen.list(gen.int(-5, 5), 20), 200, fn(xs) {
    let ys = normalize(xs)
    expect(List.zip(ys, List.slice(ys, 1, List.len(ys))).all(fn(p) { p.0 < p.1 }))
  })
}
```

Runs are seeded. `sakura test --json` reports one record per test with its seed, and `sakura test --seed N --filter "normalize"` reruns a case.

## Property tests from contracts (planned) [#property-tests-from-contracts-planned]

The planned `sakura test --contracts` will write property tests you did not write ([design §7.4](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). For every `pub` function with at least one refined parameter or `requires` clause, it:

1. Generates inputs jointly from the parameter refinements and `requires`. It uses constructive generators for ranges, lengths, enums, and non-empty lists, and falls back to filtering.
2. Runs the function under the test default handlers.
3. Checks every `ensures` clause and the result's refinements.

Failures are shrunk and stored under `tests/regressions/<fn>.json`. If filtering discards more than 90% of candidates, the runner warns with `W7001`. Functions that use effects other than `Fail`, `Clock`, and `Random` are skipped unless handlers are supplied with a `@contract_test(handlers: [Sql.memory(), Log.captured()])` attribute.

Given `clamp` from [Contracts](/docs/tour/contracts), The planned `sakura test --contracts` will generate `(x, lo, hi)` with `lo <= hi` and checks `lo <= result && result <= hi` on each run.

## Snapshot tests [#snapshot-tests]

`expect_snapshot` compares a value's [`Show` output](/docs/tour/types#show-output) with a stored file at `tests/__snapshots__/<module>/<name>.snap`. Here the stored snapshot is `[1, 2, 3]`:

```sakura
test "rendering is stable" {
  expect_snapshot("normalize_doc", normalize([3, 1, 3, 2]))
}
```

The `Show` format is the same on every backend, so snapshots do not depend on the target. `Secret<T>` is not `Show`, so a secret cannot end up in a snapshot. After a reviewed change, `sakura test --update-snapshots` rewrites the stored files.

## Running tests [#running-tests]

```bash
sakura test                                  # all tests and properties
sakura test --filter "normalize"             # tests whose name matches
sakura test --seed 42                        # fix the seed
# Planned M3 completion: sakura test --contracts
sakura test --json                           # JSON Lines records for agents
sakura test --update-snapshots               # rewrite snapshot files
```

The runner applies per-test time and memory limits and denies network access. `sakura test` exits with `3` when tests fail and `1` when the program has compile errors. A passing test gives its claims the `tested` status in the [guarantee model](/docs/tour/contracts#the-guarantee-model), never `proved`.

## What the compiler and runner check [#what-the-compiler-and-runner-check]

- Tests and properties are type and effect checked like any other code.
- Every effect a test uses, other than `Fail`, is handled by a default or by a `with` in the test (`V3020`).
- Generated arguments satisfy parameter types, refinements, and `requires`.
- Test and property bodies are part of the frozen spec during agent repair (`V8020`), so an agent cannot edit a test to make it pass.

## Next [#next]

[Capabilities and security](/docs/tour/capabilities): how handlers become authority.
