Rho Rho language and toolchain

Testing

A test is an ordinary Rho program, so it runs on one backend: whatever the
project generates.

Running them

rho test <project-dir>

The runner discovers the tests under tests/, builds the project, and runs the
generated binary for each one. --target go runs them on the Go backend, which
is the one most projects generate.

What a test file looks like

module tests.option_test

import std.core.option

test "Some holds a value" {
    let value = Some(7)
    assert(is_some(value))
}

test "None holds nothing" {
    let value = None[u8]()
    assert(is_none(value))
}

The file is a module, so it imports what it exercises. A test that cannot import
the thing it is about is a test about something else.

Assertions

std.assert holds the assertions:

AssertionWhat it says
assert(condition)The condition holds
assert_eq(left, right)Two values are equal, and a failure reports both
assert_ne(left, right)Two values differ
assert_true(value) / assert_false(value)A boolean is what it is
assert_err(result)A Result is an error
assert_ok(result)A Result is a value

A failure reports the file, the line and what it expected, because a test that
says only "failed" is a test nobody acts on.

Testing the toolchain itself

The Go side of the repository is tested with go test:

go test ./...

The components and plugins are separate Go modules, so they are tested from their
own trees:

cd modules/components/rho-fmt && go test ./...

The formatter is checked for round-trip safety: formatting a file and parsing the
result yields the same tree it started from. A formatter that loses code is
worse than no formatter, so anything it does not understand is emitted as a
visible marker rather than dropped.

The documentation plugin tests

A plugin is another process, so the documentation plugin is tested over the
protocol itself: a request is written to its standard input, and its response is
read back. That covers the contract rather than the markup, which is what a
plugin owns.