taptest

function taptest( actual, expect [, compare [, message ]] ) --> msg
function taptest( diagnostic ) --> msg
function taptest() --> msg

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

msg

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

local test_count = 0
local fail_count = 0

--ZFUNC-taptest-v0
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 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, info = 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

      if info then msg = msg.." "..info 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

Examples

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

-- 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 taptest is checked

-- wrap the taptest to avoid printing test results on the stdout
local fake_test_count = 1
local taptest_wrapped = taptest
local function taptest( ... )
   if 1 < select( "#", ... ) then print( "ok "..fake_test_count ) end
   fake_test_count = fake_test_count + 2
   local _p = print
   print = function() end
   local result = taptest_wrapped( ... )
   print = _p
   return result
end

t( taptest( 1, 1 ), "ok 1" )

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

-- Additional infos when the test fails
t( taptest( 1, 2 ),
   "not ok 5 - taptest.ex1.lua:18. Expectation [2] does not match with [1]. " )

-- Custom infos on fail
t( taptest( 1, 2, "Not good!" ),
   "not ok 7 - taptest.ex1.lua:18. Expectation [2] does not match with [1]. Not good!" )

-- Custom compare function
t( taptest( 1, 2, function( a, b ) return a < b end ),
   "ok 9" )
t( taptest( 2, 1, function( a, b ) return a < b end ),
   "not ok 11 - taptest.ex1.lua:18. Expectation [1] does not match with [2]. " )

-- Custom compare function and message
t( taptest( 2, 1, function( a, b ) return a < b end, "Not good!" ),
   "not ok 13 - taptest.ex1.lua:18. Expectation [1] does not match with [2]. Not good!" )
t( taptest( 2, 1, "Not good!", function( a, b ) return a < b end ),
   "not ok 15 - taptest.ex1.lua:18. Expectation [1] does not match with [2]. Not good!" )

-- Single argument = Tap diagnostic
t( taptest( "new\nsuite" ), "#\n#########\n# new\n# suite" )

-- Checker function can add useful information
t( taptest( 1, 1, function( a, b ) return a == b, "- additional info" end ),
   "ok 18 - additional info" )

t( taptest( 1, 2, function( a, b ) return a == b, "- additional info" end ),
   "not ok 20 - taptest.ex1.lua:18. Expectation [2] does not match with [1].  - additional info" )

-- No argument = Summary and final plan
t( taptest(), "#\n#########\n# 6 tests failed\n1..21" )

t()

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