burn / docs

Tools

Testing

A test in Burn is a program: it passes when it exits with 0 and fails when it stops with a runtime error, a failed assert or a non-zero exit code. Tests live in the tests/ folder of a project, one program per file, and burn test runs each of them in its own process.

colors/
├── burn.toml
├── src/lib.bn
└── tests/
    ├── convert.bn
    └── parse_errors.bn

A first test

import "std/testing"
import "github.com/ada/colors"

assert(toHex(Color { r: 255, g: 0, b: 0 }) == "#ff0000", "red")
assert(toHex(Color { r: 0, g: 0, b: 0 }) == "#000000", "black")
print("all tests passed")

assert(condition, message) stops the program with runtime error: assertion failed: <message> when the condition is false. A test imports the project's own package by its name, like any other code that uses it.

burn test                 # every file in tests/
burn test convert         # only the files whose name contains "convert"
ash test                  # the same, through ash
testing github.com/ada/colors (2 files)
test tests/convert.bn ... ok
test tests/parse_errors.bn ... FAILED

---- tests/parse_errors.bn ----
runtime error: assertion failed: empty input is rejected
 --> tests/parse_errors.bn:7:1
  |
7 | assert(parse("") == null, "empty input is rejected")
  | ^

1 passed, 1 failed

burn test exits with 1 when a test fails, so it can gate a CI job. In a workspace it runs the tests of every member, and -p <member> picks one.

Many checks in one file

std/testing also groups checks into named tests that keep going after a failure and report at the end:

import "std/testing"

fun slug(title: string): string {
    return lower(replace(trim(title), " ", "-"))
}

test("spaces become dashes", fun() {
    expectEqual(slug("Hello World"), "hello-world")
})

test("the ends are trimmed", fun() {
    expectEqual(slug("  Burn  "), "burn")
})

test("numbers", fun() {
    expectNear(0.1 + 0.2, 0.3, 0.000001)
    expectNotEqual(slug("a b"), "a b")
})

finish()
ok   spaces become dashes
ok   the ends are trimmed
ok   numbers
3 passed, 0 failed
Function Checks
assert(condition, message) stops the program right away when condition is false
test(name, body) runs body, then prints ok or FAIL with the name
expect(condition, message) condition is true
expectEqual(actual, expected) the two are equal with ==; the message shows both
expectNotEqual(actual, unwanted) the two differ
expectNear(actual, expected, tolerance) two floats are at most tolerance apart
finish() prints the totals and exits with 1 if any test failed

The expect functions record a failure in the current test instead of stopping, so one run shows every broken check. Always end the file with finish(), otherwise a failed expect does not change the exit code.

Testing errors and output

A test can check that code reports a failure through its result:

import "std/fs"
import "std/testing"

test("reading a missing file", fun() {
    expect(!fileExists("does/not/exist.txt"), "the file should be missing")
    expect(!Files.isDir("does/not"), "the folder should be missing")
})

finish()

To check what a program prints, or that it stops with a runtime error, run it as a separate process with Process.output from std/process and compare its out, err and code.

Tests on every backend

A test is an ordinary program, so it can run on each backend:

burn test                                  # on bvm
burnc tests/convert.bn -o /tmp/t && /tmp/t  # natively
burnc tests/convert.bn --target js -o t.js && node t.js

In CI

- run: burn fmt --check src/*.bn tests/*.bn
- run: burn check
- run: burn test

burn check type-checks every source, test and example without running them, and burn fmt --check fails when a file is not formatted. See Burn standards for how to name and organize tests.