Lua Notes

Run tests
Login
  1. Libraries can run tests when directly run
  2. check()
  3. run code only when check() isn't disabled
  4. assert()
  5. nester
  6. Run tests only if a pragma or Lua definition is set

Libraries can run tests when directly run

local fib = @record{}

function fib.recursive(n: integer, current: integer, next: integer): integer
  if n == 0 then return current end
  return fib.recursive(n - 1, next, current + next)
end

## if fib.scope.parent.is_root then
  require 'string'
  local function clock(): integer <cimport, nodecl, cinclude '<time.h>'> end

  print('time:', clock())
  ## for _, n in ipairs({5, 15, 30, 40}) do
    print(string.format('fib(%d) = %d', #[n]#+1, fib.recursive(#[n]#, 0, 1)))
    print('time:', clock())
  ## end
## else
  return fib
## end

Usage as a library and as a script:

$ nelua -i 'local fib = require("fibonacci") print(fib.recursive(15,0,1))'
610
$ nelua fibonacci.nelua 
time:	497
fib(6) = 5
time:	559
fib(16) = 610
time:	567
fib(31) = 832040
time:	572
fib(41) = 102334155
time:	577

Run tests with check()

check() is like assert(), but is removed in release builds, so you could it for checks you don't mind running all the time in debug builds. The sqrt() candidates in the next example would likely be fine. This is also great for contract programming:

-- Example from "Programming in Ada 2012", by John Barnes.
require 'vector'
local stack = @record{data: vector(integer), limit: integer}
function stack:isempty() return 0 == #self.data end
function stack:isfull() return self.limit >= #self.data end
function stack:push(x: integer)
  check(not self:isfull())            -- precondition
  defer check(not self:isempty()) end -- postcondition

  self.data:push(x)
end
function stack:pop()
  check(not self:isempty())
  defer check(not self:isfull()) end

  return self.data:pop()
end
function stack:__eq(other: stack)
  return self.data  == other.data
     and self.limit == other.limit
end

return stack

Usage:

$ nelua -i 'local stack = require("stack")  local x: stack  x:push(1)'
stack.nelua:7:9: runtime error: assertion failed!
  check(not self:isfull())            -- precondition
        ^~~~~~~~~~~~~~~~~

Oops, zero-initialized stacks can't be pushed to, because the depth limit is 0.

run code only when check() isn't disabled

To test the first return value of a function call, it's enough to have a list of check()s:

check(2  == machine{1,0,0,0,99})
check(2  == machine{2,3,0,3,99})
check(2  == machine{2,4,4,5,99,0})
check(30 == machine{1,1,1,4,99,5,6,0,99})

But if you start needing to test more than a single return, you'll need to store variables somewhere. The way to do this without having to reorganize your testing significantly, is to test the same pragma that check() itself tests, pragmas.nochecks:

## if not pragmas.nochecks then
do
  local op, m = decode(1002)
  check(op == Opcode.Mul)
  check(m[0] == Mode.Position)
  check(m[1] == Mode.Immediate)
  check(m[2] == Mode.Position)
end
## end

Run tests with assert()

It's not so nice as the next option, but a minimal option is assert() gated by Lua (i.e., Nelua's preprocessor). In the following testmath has no builtin meaning, it's just a keyword decided on the spot, that can be supplied with the -P flag to nelua.

local function sqrt(n: integer): integer
  require 'math'
  return math.sqrt(n)
end

## if pragmas.testmath then
  assert(sqrt(1) == 1)
  assert(sqrt(4) == 2)
  assert(sqrt(9) == 3)
  assert(sqrt(16) == 5)
## end

As tested:

$ nelua mysqrt.nelua  # no output
$ nelua -P testmath mysqrt.nelua
mysqrt.nelua:10:19: runtime error: assertion failed!
  assert(sqrt(16) == 5)
                  ^~~~

Aborted
$ nelua -d -P testmath mysqrt.nelua  # shows C line numbers and backtrace, including nelua_assert_line_4 and nelua_abort functions
...
$ nelua -o test -P testmath mysqrt.nelua && gdb ./test
(gdb) b nelua_abort
(gdb) run
(gdb) bt   # confirm backtrace again
(gdb) up
(gdb) up
(gdb) list
183	}
184	int nelua_main(int argc, char** argv) {
185	  nelua_assert_line_1((mysqrt_sqrt(1) == 1));
186	  nelua_assert_line_2((mysqrt_sqrt(4) == 2));
187	  nelua_assert_line_3((mysqrt_sqrt(9) == 3));
188	  nelua_assert_line_4((mysqrt_sqrt(16) == 5));
189	  return 0;
190	}
191	int main(int argc, char** argv) {
192	  return nelua_main(argc, argv);
(gdb) p mysqrt_sqrt(16)
$1 = 4
(gdb) p 4 == 5
$2 = 0

Run tests with nester

For nice output you can use nester from edubart/nelua-batteries. For example:

require 'nester'
local regexp = require("pcre")

nester.describe('pcre', function()
  nester.it('regex', function()
    local matches: [6]cint
    local re <close> = regexp.MustCompile('^t \\d+ / (?P<goal>\\d+) \\(-?\\d+\\) = \\d+%$', &matches, #matches)
    local str = 't 400 / 1600 (1200) = 25%'
    expect.truthy(re:Match(str))
    expect.equal(re:Group(str, 1), "1600")
    expect.equal(re:Group(str, 1), "1601")
  end)
end)

nester.report()

Has brightly colored output:

[FAIL] pcre | regex (test_pcre.nelua:5)
test_pcre.nelua:11: expected value be equal
first value:
1600
second value:
1601

[====] pcre | 0 successes / 1 failures / 0.000454 seconds
0 successes / 1 failures / 0.000513 seconds

This can be in a separate file by itself that loads your application's libraries and tests them 'externally', with a build system running each test separately, e.g., nelua test_pcre.

You can also have inline tests gated by Lua:

local function f() return 1 end

local function main()
  print('f', f())
end

## if pragmas.testrun then
  require 'nester'
  nester.describe('example', function()
    nester.it('f', function()
      expect.equal(f(), 2)
    end)
  end)
## else
  main()
## end

Usage:

$ nelua example.nelua 
f	1
$ nelua -P testrun example.nelua -L$HOME/nelua/nelua-batteries
warning: using error handling module, it is highly experimental and incomplete!
[FAIL] example | f (example.nelua:10)
example.nelua:11: expected value be equal
first value:
1
second value:
2

[====] example | 0 successes / 1 failures / 0.000216 seconds