Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TAP-compatible test function #4

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 67 additions & 0 deletions sys/taptest/taptest.adoc
@@ -0,0 +1,67 @@
= taptest

[source,lua]
----
function taptest( actual, expect, [ compare, [ message ]] ) --> string
function taptest( diagnostic ) --> string
function taptest( ) --> string
----

== Description

This function behaves differently based on the number of arguments. It can
check actual values versus expected ones. It can print diagnostic. Or it can
print tests summary when called without arguments.

All the output is done in the Test Anything Protocol (TAP) format. In case of
failure some information are appended, like source position, actual value, etc.

For a more detailed explanation of its behaviour, refer to the next section.

== Parameters

actual::
The actual value got from the code under test.

expect::
The expected value

compare::
The compare function. If it is given as 3-rd or 4-th argument, this function
will be called with 'actual, expected' as argument. If it return true the test
will be assumed to success, otherwise it will be assumed to be failed. If no
compare function is given, the '==' operator will be used as default.

message::
If a message string is given as 3-rd or 4-th argument, it will be appended to
the TAP formatted line, only in case of failing test. This is ment as a way to
give additional information about the failure.

diagnostic::
When called with just one string argument, a TAP diagnostic block will be
printed. A '#' will be prepended to each line of the diagnostic message.

== Return Values

Returns a string containing the same message written to the stdout. This
message is a TAP check line or a sequence of TAP diagnostic lines.

== Code

[source,lua]
----
include::taptest.lua[]
----

== Examples

[source,lua]
----
include::taptest.ex1.lua[]
----

== Inspired by

https://testanything.org/
https://github.com/telemachus/tapered

79 changes: 79 additions & 0 deletions sys/taptest/taptest.ex1.lua
@@ -0,0 +1,79 @@

-- taptest is both the "Unit under test" (uut) and the "Test framework" (tf)
local uut = require( "taptest" )
local tf = require( "taptest" )

-- To avoid confusion (as much as it is possible) tf will be always used in its
-- easest form: it just checks that the two argument are equals.
-- Since taptest always returns what it print on stdout, the returned
-- value of uut is checked

tf(
uut( 1, 1 ),
'ok 1' )

-- Note: since at each line two test will be done (one for uut and one for tf)
-- the test counter step is 2, not 1
tf(
uut( 1, 1 ),
'ok 3' )

-- Avoid literal line number in the test expectaion
local function srctag()
local i = debug.getinfo(2)
return i.source:match("[^/\\]*$") .. ':' .. (i.currentline-1)
end

-- Trick to mask the failing of uut in the stdout
local function mask_next_uut_failing_line()
io.stdout:write('# uut line (ignore): ')
end

-- Additional infos when the test fails
mask_next_uut_failing_line()
tf(
uut( 1, 2 ),
'not ok 5 - '..srctag()..'. Expectation [2] does not match with [1]. ' )

-- Custom infos on fail
mask_next_uut_failing_line()
tf(
uut( 1, 2, 'Not good!' ),
'not ok 7 - '..srctag()..'. Expectation [2] does not match with [1]. Not good!' )

-- Custom compare function
tf(
uut( 1, 2, function(a,b) return a < b end ),
'ok 9' )
mask_next_uut_failing_line()
tf(
uut( 2, 1, function(a,b) return a < b end ),
'not ok 11 - '..srctag()..'. Expectation [1] does not match with [2]. ' )

-- Custom compare function and message
mask_next_uut_failing_line()
tf(
uut( 2, 1, function(a,b) return a < b end, 'Not good!' ),
'not ok 13 - '..srctag()..'. Expectation [1] does not match with [2]. Not good!' )
mask_next_uut_failing_line()
tf(
uut( 2, 1, 'Not good!', function(a,b) return a < b end ),
'not ok 15 - '..srctag()..'. Expectation [1] does not match with [2]. Not good!' )

-- Single argument = Tap diagnostic
tf(
uut( 'new\nsuite' ),
'#\n#########\n# new\n# suite' )

-- No argument = Summary and final plan
mask_next_uut_failing_line()
tf(
uut( ),
'1..17' )

tf()

-- In case all the tests are successful, the line
-- # all is right
-- will be substitued to the '# 5 tests failed' one

78 changes: 78 additions & 0 deletions sys/taptest/taptest.lua
@@ -0,0 +1,78 @@
--ZFUNC-taptest-v1

local test_count = 0
local fail_count = 0

local function taptest(...) --> msg

local function diagnostic(desc)
local msg = '#\n#########\n# ' .. desc:gsub('\n','\n# ')
print(msg)
return msg
end

local function print_summary()
local msg = ''
-- if fail_count == 0 then
-- msg = msg .. diagnostic('all is right')
-- else
-- msg = msg .. diagnostic(fail_count.. ' tests failed')
-- end
local plan = '1..'..test_count
print(plan)
return plan
-- return msg..'\n'..plan
end

local function do_check(got, expected, a, b)

-- Extra arg parse and defaults
local checker, err
if 'string' == type(a) then err = a end
if 'string' == type(b) then err = b end
if not err then err = '' end
if 'function' == type(a) then checker = a end
if 'function' == type(b) then checker = b end
if not checker then checker = function(e, g) return e == g end end

-- Check the condition
test_count = test_count + 1
local ok = checker(got, expected)

-- Generate TAP line
local msg = ''
if ok then
msg = msg .. 'ok ' .. test_count
else
fail_count = fail_count + 1

-- Find position in source
local stackup = 2
local i = debug.getinfo(stackup)
while i.source == '=(tail call)' do
stackup = stackup + 1
i = debug.getinfo(stackup)
end

msg = msg
.. 'not ok ' .. test_count .. ' - '
.. i.source:match('([^/\\]*)$') .. ':' .. i.currentline .. '. '
.. 'Expectation [' .. tostring(expected) .. '] '
.. 'does not match with [' .. tostring(got) ..']. '
.. err
end

print(msg)
return msg
end

local narg = select('#', ...)
if 0 == narg then return print_summary()
elseif 1 == narg then return diagnostic(select(1, ...))
elseif 4 >= narg then return do_check(...)
end
return nil, 'Too many arguments'
end

return taptest