SCUA

How-to

Test your code

SCUA has a built-in test runner. You write test functions in *_test.scua files, assert what you expect, and run scua test. It reports pass/fail and exits non-zero on failure, so it fits straight into CI.

#A first test

Put your tests in a file whose name ends in _test.scua, and write each test as a function named test_*:

-- inventory_test.scua
fn add_item(bag, item)
  bag.push(item)
  return bag
end

fn test_add_one()
  let bag = add_item([], "sword")
  assert_eq(len(bag), 1)
end

fn test_add_two()
  let bag = add_item(add_item([], "sword"), "shield")
  assert_eq(bag[1], "shield")
end

Run the tests from the directory that holds them:

$ scua test
PASS  ./inventory_test.scua  test_add_one
PASS  ./inventory_test.scua  test_add_two

2 passed, 0 failed (1 file)

scua test finds every *_test.scua file under the current directory (or a directory you name: scua test tests/). Each test_* function runs on its own, so one failing test doesn't stop the others.

#Assertions

The runner doesn't need a special assertion library; the assert family is built in.

  • assert(cond, msg?) faults with msg unless cond is truthy.
  • assert_eq(got, want) faults unless the two values are equal, and the message shows both.
  • assert_ne(a, b) faults if the two values are equal.
  • assert_near(got, want, eps) faults unless got and want are within eps of each other. Use it for floats, which aren't exact.

A failing comparison tells you what it saw:

fn test_math()
  assert_eq(2 + 2, 5)
end
$ scua test
FAIL  ./math_test.scua  test_math assert_eq failed: expected 5, got 4

0 passed, 1 failed (1 file)

#How a test fails

A test fails when it raises a fault that nothing catches: a failed assert, a runtime fault like an out-of-range index, or an explicit error(...). Anything that would stop a normal program stops the test and marks it failed, with the located message. A test that runs to the end without faulting passes.

Because each test_* runs in isolation, a fault in one is reported and the rest keep going. If a file has a syntax, type, or compile error, the whole file is reported as an error and its tests count as failures.

#Files without test functions

If a *_test.scua file has no test_* functions, the whole file is treated as a single test: it passes if it runs without an uncaught fault. This is handy for a quick check or a script-style test.

-- smoke_test.scua
assert(1 < 2, "basic sanity")
assert_near(0.1 + 0.2, 0.3, 0.001)

#A note on actors

The runner shines for synchronous logic: pure functions, data transforms, validation, anything you can call and assert on directly. Testing a partition's message handling is harder, because a partition processes its messages after your test function has already returned, so you can't tell an actor and assert on the result in the same test. For now, test the plain functions your handlers call (keep the logic out of the handler where you can), and check end-to-end actor behaviour by running a small program and looking at its output.