Heroes / Guide / chapter 13

Tests and holes

Writing tests in the source and using ??? to complete code.

A test checks code in the same file. Use ??? to leave an expression unfinished and ask the compiler which type it expects. Both forms are part of the language.

test is a declaration

test "the part that exists, works"
    lunch = [Entry(name: "soup", calories: 120), Entry(name: "bread", calories: 250)]
    assert total_calories(lunch) == 370
examples/gallery/09-holes.hero, lines 30–32

A test has a quoted description and an indented body. You can write it in the same file as the code it checks. Ordinary builds exclude it from the executable; no extra library, separate directory or special file name is required.

assert requires a bool. If the check fails, it shows the expression. For comparisons, it also shows both values when they can be printed, such as numbers, strings or booleans:

$ heroes test points.hero
assert failed: dist2(a: Point(x: 0, y: 0), b: Point(x: 3, y: 4)) == 24
  left:  25
  right: 24
FAIL "the 3-4-5 triangle"
ok   "a rectangle keeps its size when it moves"
2 tests, 1 failed

The other tests still run and still report. The exit status is non-zero, so this works in a script without anyone parsing the output.

??? is a legal expression

Write ??? where a value is missing. The compiler checks the surrounding code and reports the type needed to complete the expression.

# Written, tested, done.
function total_calories(entries: [Entry]) -> i64
    total: i64 @= 0

    for e in entries
        total @ total + e.calories

    return total
examples/gallery/09-holes.hero, lines 12–19
# Next up: the compiler will name the expected type and list `entries`,
# `total_calories` and the fields of `Entry` when it reaches this.
function heaviest(entries: [Entry]) -> Entry
    ???
examples/gallery/09-holes.hero, lines 21–24
# The hole can be one argument of an otherwise finished line.
function summary_of(entries: [Entry]) -> str
    return "total " + total_calories(entries).to_str() + ", heaviest " + ???
examples/gallery/09-holes.hero, lines 26–28

Ask the compiler about that file and it answers:

$ heroes check holes.hero
hole at holes.hero:24:5
  position: statement — any type, or none
  this function returns: Entry
  in scope:
    entries: [Entry]
hole at holes.hero:28:74
  expected type: str
  in scope:
    entries: [Entry]
  functions that return it:
    summary_of(entries: [Entry])
      # The hole can be one argument of an otherwise finished line.

In the second message, the compiler reports that a str is needed. It then lists available names and functions that return that type, along with the first line of each function’s comment. This helps you choose how to complete the code.

Everything else still gets checked

A file with unfinished expressions is checked in full. The check exits with status 0 only if there are no other errors. While a ??? remains, the compiler produces no executable and identifies the code to complete:

$ heroes test holes.hero
no binary: 2 holes in holes.hero
hole at holes.hero:24:5
  position: statement — any type, or none
  this function returns: Entry
  in scope:
    entries: [Entry]
hole at holes.hero:28:74
  expected type: str
  in scope:
    entries: [Entry]
  functions that return it:
    summary_of(entries: [Entry])
      # The hole can be one argument of an otherwise finished line.

The compiler can therefore check a half-written program without mistaking it for a finished one. A hole cannot reach an executable.

Why this is in the language rather than in an editor

A person or model can get this information directly from the compiler, without installing an editor or extensions. The three characters ??? mark the unfinished code in the source.

function main()
    print(summary_of([Entry(name: "tea", calories: 2)]))
examples/gallery/09-holes.hero, lines 34–35

Complete both expressions to run main. Meanwhile, the compiler can already check the rest of the file.

Next: The C boundary, where everything the language does not have comes from. Back to the chapter list. ← Previous: Modules and files

Run a program of your own: it takes four commands. Or take the whole language in one file, and every program already written in it.