Skip to content
Kavachकवच

Testing: test blocks

● Intermediate3 min

A test block checks a story of several steps that contracts can't express.

type Order {
    id: Int
    paid: Bool
}

fn total_paid(orders: [Order]) -> Int {
    let n = 0
    for o in orders {
        if o.paid {
            n += 1
        }
    }
    n
}

test "only paid orders count" {
    const list = [Order(id: 1, paid: true), Order(id: 2, paid: false), Order(id: 3, paid: true)]
    assert total_paid(list) == 2
}

The program ran and printed:

kavach run story.kav

✓ test "only paid orders count"1 passed, 0 failedseed 7✓ total_paid  200 cases1 functions   1 ✓   0 ✗   1 weakly testedweakly tested (no contract or example; only checked for panics):  story.kav:6   total_paid
story.kav: Kavach code, then what running it prints

assert checks a condition. The report also flags total_paid as weakly tested: it has a story but no contract, so only some of its behaviour is checked.

use std.random

fn roll() -> Int uses Random
    ensure 1 <= result <= 6
{
    random.int(1, 7)
}

test "rolls stay on the die" {
    for _ in 0..100 {
        assert 1 <= roll() <= 6
    }
}

The program ran and printed:

kavach run random.kav

✓ test "rolls stay on the die"1 passed, 0 failedseed 7✓ roll  1 cases1 functions   1 ✓   0 ✗   0 weakly tested
random.kav: Kavach code, then what running it prints

Tests use nothing unless given

A test runs with no capabilities unless its name is followed by uses. Reaching one without it, directly or through a function, is a compile error:

use std.fs

fn save(text: String) ! FsError uses Fs {
    try fs.write_text("report.txt", text)
}

test "writes a report" {
    try save("hi")
}

The compiler refused the program:

kavach run test-uses.kav

error: this test uses Fs, but a test runs with no capabilities unless given some: write `uses Fs` after its name, or give it a fake with `with` (spec 11.8)
test-uses.kav: Kavach code, then what running it prints

Say what the test needs with uses. A test given Fs, Net, Env or Clock reaches the real one. Add with Fs = fake to get an empty file system in memory instead, never full, so nothing the test writes reaches the disk:

use std.fs

fn save(text: String) ! FsError uses Fs {
    try fs.write_text("report.txt", text)
}

test "writes a report" uses Fs with Fs = fake {
    try save("hi")
    assert (try fs.read_text("report.txt")) == "hi"
}

The program ran and printed:

kavach run test-uses.kav

✓ test "writes a report"1 passed, 0 failedseed 7✓ save  200 cases1 functions   1 ✓   0 ✗   1 weakly testedweakly tested (no contract or example; only checked for panics):  test-uses.kav:3   save
test-uses.kav: Kavach code, then what running it prints
  • The console, the clock and random numbers are always there: a test run captures the output, can freeze the clock and seeds the numbers.
  • A test can also hand in its own implementation of a capability: test "refund flow" with Payments = FakePayments() { … }.

with Net = fake, with Env = fake and with Clock = fake give the other fakes the same way.

A test ends with its tasks

A test block is a function of its own for its tasks: those it spawned and left running are cancelled when it ends, as a function's are (Tasks Never Leak). Here the worker still waits for a job when the test ends; it is cancelled, and the run goes on. A server a test started doesn't keep the run going either.

/// Waits for jobs, one at a time, until the channel closes.
fn worker(jobs: Receiver<Int>, done: Sender<Int>) {
    for job in jobs {
        done.put(job * 10)
    }
}

test "a worker left waiting ends with the test" {
    const (send, jobs) = Channel<Int>(capacity: 1)
    const (done, results) = Channel<Int>(capacity: 1)
    const _worker = spawn worker(jobs, done)
    send.put(4)
    assert results.take() == Taken.Item(value: 40)
}

The program ran and printed:

kavach run left-task.kav

✓ test "a worker left waiting ends with the test"1 passed, 0 failedseed 70 functions   0 ✓   0 ✗   0 weakly tested  not generated: worker: takes values that can't be generated yet
left-task.kav: Kavach code, then what running it prints

A test, an example or a generated case that runs past sixty times time_budget (a minute by default) is stopped. kavach test names it and fails, so one waiting for something that never comes can't hang the run:

error: test "…" has run for N s, past the limit of M s, and was stopped (spec 11.9)
kavach test: Text code

Where test blocks go

Beside the code they test: the tests of orders.kav go in orders.test.kav, in the same folder. A glance at a folder shows which modules have hand-written tests.

pub type Order {
    pub amount: Int
    pub paid: Bool
}

/// What the paid orders add up to.
pub fn total_paid(orders: [Order]) -> Int
    require orders.len() <= 1_000
    require orders.all(o => o.amount >= 0 and o.amount <= 1_000_000)
    example total_paid([]) == 0
{
    orders.filter(o => o.paid).sum(o => fee(o.amount))
}

fn fee(amount: Int) -> Int
    require amount >= 0 and amount <= 1_000_000
    example fee(10) == 11
{
    amount + 1
}
src/orders.kav and 2 more files: Kavach code
terminal
kavach run
27
kavach test --seed 7
✓ total_paid: example total_paid([]) == 0
✓ fee: example fee(10) == 11
✓ test "only paid orders count"
✓ test "the fee is one"
4 passed, 0 failed
seed 7
✓ total_paid  200 cases
✓ fee  200 cases
2 functions   2 ✓   0 ✗   0 weakly tested
  • orders.test.kav is part of the module orders: it uses the private fee, with no use.
  • kavach run leaves the tests out; kavach test runs them with the examples and the generated tests.
  • A story across many modules goes in the project's tests/ folder instead, and a test block may also sit in the file it tests.
  • A test file must be named after a module beside it. Otherwise it is an error that says so:
test "an invoice adds up" {
    assert 1 + 1 == 2
}

The compiler refused the program:

kavach run

error: `invoice.test.kav` holds the tests of `invoice.kav`, but there is no `invoice.kav` beside it: name it after the module it tests (spec 11.8)
src/invoice.test.kav and 1 more file: Kavach code, then what running it prints