123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- local function TERMINAL_HANDLER(e, test, msg, errormsg)
- if errormsg then
- errormsg = ": "..errormsg
- else
- errormsg = ""
- end
- if e == 'start' then
- print("######## "..e.."ed "..test.." tests")
- elseif e == 'pass' then
- print(" "..e.." "..test..': '..msg)
- elseif e == 'fail' then
- print(" ==> "..e.." "..test..': '..msg..errormsg)
- elseif e == 'except' then
- print(" ==> "..e.." "..test..': '..msg..errormsg)
- elseif e == 'finish' then
- print("######## "..e.."ed "..test.." tests")
- else
- print(e.." "..test)
- end
- end
- -- implement pseudo task handling for on host testing
- local drain_post_queue = function() end
- if not node then -- assume we run on host, not on MCU
- local post_queue = {{},{},{}}
- drain_post_queue = function()
- while #post_queue[1] + #post_queue[2] + #post_queue[3] > 0 do
- for i = 3, 1, -1 do
- if #post_queue[i] > 0 then
- local f = table.remove(post_queue[i], 1)
- if f then
- f()
- end
- break
- end
- end
- end
- end
- -- luacheck: push ignore 121 122 (setting read-only global variable)
- node = {}
- node.task = {LOW_PRIORITY = 1, MEDIUM_PRIORITY = 2, HIGH_PRIORITY = 3}
- node.task.post = function (p, f)
- table.insert(post_queue[p], f)
- end
- node.setonerror = function(fn) node.Host_Error_Func = fn end -- luacheck: ignore 142
- -- luacheck: pop
- end
- --[[
- if equal returns true
- if different returns {msg = "<reason>"}
- this will be handled spechially by ok and nok
- --]]
- local function deepeq(a, b)
- local function notEqual(m)
- return { msg=m }
- end
- -- Different types: false
- if type(a) ~= type(b) then return notEqual("type 1 is "..type(a)..", type 2 is "..type(b)) end
- -- Functions
- if type(a) == 'function' then
- if string.dump(a) == string.dump(b) then
- return true
- else
- return notEqual("functions differ")
- end
- end
- -- Primitives and equal pointers
- if a == b then return true end
- -- Only equal tables could have passed previous tests
- if type(a) ~= 'table' then return notEqual("different "..type(a).."s expected "..a.." vs. "..b) end
- -- Compare tables field by field
- for k,v in pairs(a) do
- if b[k] == nil then return notEqual("key "..k.."only contained in left part") end
- local result = deepeq(v, b[k])
- if type(result) == 'table' then return result end
- end
- for k,v in pairs(b) do
- if a[k] == nil then return notEqual("key "..k.."only contained in right part") end
- local result = deepeq(a[k], v)
- if type(result) == 'table' then return result end
- end
- return true
- end
- -- Compatibility for Lua 5.1 and Lua 5.2
- local function args(...)
- return {n=select('#', ...), ...}
- end
- local function spy(f)
- local mt = {}
- setmetatable(mt, {__call = function(s, ...)
- s.called = s.called or {}
- local a = args(...)
- table.insert(s.called, {...})
- if f then
- local r
- r = args(pcall(f, unpack(a, 1, a.n)))
- if not r[1] then
- s.errors = s.errors or {}
- s.errors[#s.called] = r[2]
- else
- return unpack(r, 2, r.n)
- end
- end
- end})
- return mt
- end
- local function getstackframe()
- -- debug.getinfo() does not exist in NodeMCU Lua 5.1
- if debug.getinfo then
- return debug.getinfo(5, 'S').short_src:match("([^\\/]*)$")..":"..debug.getinfo(5, 'l').currentline
- end
- local msg
- msg = debug.traceback()
- msg = msg:match("\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t([^\t]*): in") -- Get 5th stack frame
- msg = msg:match(".-([^\\/]*)$") -- cut off path of filename
- return msg
- end
- local function assertok(handler, name, invert, cond, msg)
- local errormsg
- -- check if cond is return object of 'eq' call
- if type(cond) == 'table' and cond.msg then
- errormsg = cond.msg
- cond = false
- end
- if not msg then
- msg = getstackframe()
- end
- if invert then
- cond = not cond
- end
- if cond then
- handler('pass', name, msg)
- else
- handler('fail', name, msg, errormsg)
- error('_*_TestAbort_*_')
- end
- end
- local function fail(handler, name, func, expected, msg)
- local status, err = pcall(func)
- if not msg then
- msg = getstackframe()
- end
- if status then
- local messageParts = {"Expected to fail with Error"}
- if expected then
- messageParts[2] = " containing \"" .. expected .. "\""
- end
- handler('fail', name, msg, table.concat(messageParts, ""))
- error('_*_TestAbort_*_')
- end
- if (expected and not string.find(err, expected)) then
- err = err:match(".-([^\\/]*)$") -- cut off path of filename
- handler('fail', name, msg, "expected errormessage \"" .. err .. "\" to contain \"" .. expected .. "\"")
- error('_*_TestAbort_*_')
- end
- handler('pass', name, msg)
- end
- local nmt = {
- env = _G,
- outputhandler = TERMINAL_HANDLER
- }
- nmt.__index = nmt
- return function(testrunname)
- local pendingtests = {}
- local started
- local N = setmetatable({}, nmt)
- local function runpending()
- if pendingtests[1] ~= nil then
- node.task.post(node.task.LOW_PRIORITY, function()
- pendingtests[1](runpending)
- end)
- else
- N.outputhandler('finish', testrunname)
- end
- end
- local function copyenv(dest, src)
- dest.eq = src.eq
- dest.spy = src.spy
- dest.ok = src.ok
- dest.nok = src.nok
- dest.fail = src.fail
- end
- local function testimpl(name, f, async)
- local testfn = function(next)
- local prev = {}
- copyenv(prev, N.env)
- local handler = N.outputhandler
- local restore = function(err)
- if err then
- err = err:match(".-([^\\/]*)$") -- cut off path of filename
- if not err:match('_*_TestAbort_*_') then
- handler('except', name, err)
- end
- end
- if node then node.setonerror() end
- copyenv(N.env, prev)
- handler('end', name)
- table.remove(pendingtests, 1)
- collectgarbage()
- if next then next() end
- end
- local function wrap(method, ...)
- method(handler, name, ...)
- end
- local function cbError(err)
- err = err:match(".-([^\\/]*)$") -- cut off path of filename
- if not err:match('_*_TestAbort_*_') then
- handler('except', name, err)
- end
- restore()
- end
- local env = N.env
- env.eq = deepeq
- env.spy = spy
- env.ok = function (cond, msg) wrap(assertok, false, cond, msg) end
- env.nok = function(cond, msg) wrap(assertok, true, cond, msg) end
- env.fail = function (func, expected, msg) wrap(fail, func, expected, msg) end
- handler('begin', name)
- node.setonerror(cbError)
- local ok, err = pcall(f, async and restore)
- if not ok then
- err = err:match(".-([^\\/]*)$") -- cut off path of filename
- if not err:match('_*_TestAbort_*_') then
- handler('except', name, err)
- end
- if async then
- restore()
- end
- end
- if not async then
- restore()
- end
- end
- if not started then
- N.outputhandler('start', testrunname)
- started = true
- end
- table.insert(pendingtests, testfn)
- if #pendingtests == 1 then
- runpending()
- drain_post_queue()
- end
- end
- function N.test(name, f)
- testimpl(name, f)
- end
- function N.testasync(name, f)
- testimpl(name, f, true)
- end
- local currentCoName
- function N.testco(name, func)
- -- local t = tmr.create();
- local co
- N.testasync(name, function(Next)
- currentCoName = name
- local function getCB(cbName)
- return function(...) -- upval: co, cbName
- local result, err = coroutine.resume(co, cbName, ...)
- if (not result) then
- if (name == currentCoName) then
- currentCoName = nil
- Next(err)
- else
- N.outputhandler('fail', name, "Found stray Callback '"..cbName.."' from test '"..name.."'")
- end
- elseif coroutine.status(co) == "dead" then
- currentCoName = nil
- Next()
- end
- end
- end
- local function waitCb()
- return coroutine.yield()
- end
- co = coroutine.create(function(wr, wa)
- func(wr, wa)
- end)
- local result, err = coroutine.resume(co, getCB, waitCb)
- if (not result) then
- currentCoName = nil
- Next(err)
- elseif coroutine.status(co) == "dead" then
- currentCoName = nil
- Next()
- end
- end)
- end
- return N
- end
|