Skip to content

Commit 37cba66

Browse files
HHHartmannvsky279
authored andcommitted
Create NodeMCU test system based on gambiarra (nodemcu#2984)
* Create mispec_file.lua * Initial commit of gambiarra * Adapt gambiarra to NodeMCU * adapt to NodeMCU spacing and add nok functionality * Some refactoring to make it easier to add new functionality * Add methode `fail` to check failing code and pass error messages to output - fail can be called with a function that should fail and a string which should be contained in the errormessage. - Pass failed check reasons to output. * Create gambiarra_file.lua * Add reporting of tests that failed with Lua error * ok, nok and fail will terminate the running test * Add capability to run sync and async tests in mixed order and have a task.post inbetween them * fix gambiarra self test to also run on device (not only host) Use less ram in checking tests directly after they ran. Use nateie task.post to tame watchdog. * Update file tests + add async tmr tests * Another fix in executing async test * Catch errors in callbacks using node.setonerror * change interface to return an object with several test methods * Update README.md * Change interface of Gambiarra + add reason for failed eq * Update gambiarra documentation * Add coroutine testcases to gambiarra * Delete mispec_file.lua as it is superseeded by gambiarra_file.lua * improve regexp for stack frame extraction * Use Lua 53 debug capabilities * move actual tests upfront * remove debug code + optimization * Show errors immediately instead of at the end of the test, freeing memory earlier * Split tests to be run in 2 tranches * rename to NTest and move to new location * Add tests to checking mechanisms * Add luacheck to tests * Some pushing around of files * more (last) fixes and file juggling * Minor tweaks and forgotten checkin * Add NTest selftest to travis * Trying how to master travis * another try * restrict NTest selftest to linux
1 parent acbd7dd commit 37cba66

File tree

10 files changed

+1425
-8
lines changed

10 files changed

+1425
-8
lines changed

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ script:
3232
- if [ "$OS" = "linux" -a "$TRAVIS_PULL_REQUEST" != "false" ]; then bash "$TRAVIS_BUILD_DIR"/tools/travis/pr-build.sh; fi
3333
- cd "$TRAVIS_BUILD_DIR"
3434
- echo "checking:"
35-
- find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 echo
36-
- find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 $LUACC -p
35+
- find lua_modules lua_examples tests/NTest* -iname "*.lua" -print0 | xargs -0 echo
36+
- find lua_modules lua_examples tests/NTest* -iname "*.lua" -print0 | xargs -0 $LUACC -p
37+
- cd tests/NTest
38+
- if [ "$OS" = "linux" ]; then ../../$LUACC -e ../NTest/NTest_NTest.lua; fi
39+
- cd "$TRAVIS_BUILD_DIR"
3740
- if [ "$OS" = "linux" ]; then bash "$TRAVIS_BUILD_DIR"/tools/travis/run-luacheck-linux.sh; fi
3841
- if [ "$OS" = "windows" ]; then bash "$TRAVIS_BUILD_DIR"/tools/travis/run-luacheck-windows.sh; fi

tests/NTest/NTest.lua

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
local function TERMINAL_HANDLER(e, test, msg, errormsg)
2+
if errormsg then
3+
errormsg = ": "..errormsg
4+
else
5+
errormsg = ""
6+
end
7+
if e == 'start' then
8+
print("######## "..e.."ed "..test.." tests")
9+
elseif e == 'pass' then
10+
print(" "..e.." "..test..': '..msg)
11+
elseif e == 'fail' then
12+
print(" ==> "..e.." "..test..': '..msg..errormsg)
13+
elseif e == 'except' then
14+
print(" ==> "..e.." "..test..': '..msg..errormsg)
15+
elseif e == 'finish' then
16+
print("######## "..e.."ed "..test.." tests")
17+
else
18+
print(e.." "..test)
19+
end
20+
end
21+
22+
--[[
23+
if equal returns true
24+
if different returns {msg = "<reason>"}
25+
this will be handled spechially by ok and nok
26+
--]]
27+
local function deepeq(a, b)
28+
local function notEqual(m)
29+
return { msg=m }
30+
end
31+
32+
-- Different types: false
33+
if type(a) ~= type(b) then return notEqual("type 1 is "..type(a)..", type 2 is "..type(b)) end
34+
-- Functions
35+
if type(a) == 'function' then
36+
if string.dump(a) == string.dump(b) then
37+
return true
38+
else
39+
return notEqual("functions differ")
40+
end
41+
end
42+
-- Primitives and equal pointers
43+
if a == b then return true end
44+
-- Only equal tables could have passed previous tests
45+
if type(a) ~= 'table' then return notEqual("different "..type(a).."s expected "..a.." vs. "..b) end
46+
-- Compare tables field by field
47+
for k,v in pairs(a) do
48+
if b[k] == nil then return notEqual("key "..k.."only contained in left part") end
49+
local result = deepeq(v, b[k])
50+
if type(result) == 'table' then return result end
51+
end
52+
for k,v in pairs(b) do
53+
if a[k] == nil then return notEqual("key "..k.."only contained in right part") end
54+
local result = deepeq(a[k], v)
55+
if type(result) == 'table' then return result end
56+
end
57+
return true
58+
end
59+
60+
-- Compatibility for Lua 5.1 and Lua 5.2
61+
local function args(...)
62+
return {n=select('#', ...), ...}
63+
end
64+
65+
local function spy(f)
66+
local mt = {}
67+
setmetatable(mt, {__call = function(s, ...)
68+
s.called = s.called or {}
69+
local a = args(...)
70+
table.insert(s.called, {...})
71+
if f then
72+
local r
73+
r = args(pcall(f, unpack(a, 1, a.n)))
74+
if not r[1] then
75+
s.errors = s.errors or {}
76+
s.errors[#s.called] = r[2]
77+
else
78+
return unpack(r, 2, r.n)
79+
end
80+
end
81+
end})
82+
return mt
83+
end
84+
85+
local function getstackframe()
86+
-- debug.getinfo() does not exist in NodeMCU Lua 5.1
87+
if debug.getinfo then
88+
return debug.getinfo(5, 'S').short_src:match("([^\\/]*)$")..":"..debug.getinfo(5, 'l').currentline
89+
end
90+
91+
local msg
92+
msg = debug.traceback()
93+
msg = msg:match("\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t([^\t]*): in") -- Get 5th stack frame
94+
msg = msg:match(".-([^\\/]*)$") -- cut off path of filename
95+
return msg
96+
end
97+
98+
local function assertok(handler, name, invert, cond, msg)
99+
local errormsg
100+
-- check if cond is return object of 'eq' call
101+
if type(cond) == 'table' and cond.msg then
102+
errormsg = cond.msg
103+
cond = false
104+
end
105+
if not msg then
106+
msg = getstackframe()
107+
end
108+
109+
if invert then
110+
cond = not cond
111+
end
112+
if cond then
113+
handler('pass', name, msg)
114+
else
115+
handler('fail', name, msg, errormsg)
116+
error('_*_TestAbort_*_')
117+
end
118+
end
119+
120+
local function fail(handler, name, func, expected, msg)
121+
local status, err = pcall(func)
122+
if not msg then
123+
msg = getstackframe()
124+
end
125+
if status then
126+
local messageParts = {"Expected to fail with Error"}
127+
if expected then
128+
messageParts[2] = " containing \"" .. expected .. "\""
129+
end
130+
handler('fail', name, msg, table.concat(messageParts, ""))
131+
error('_*_TestAbort_*_')
132+
end
133+
if (expected and not string.find(err, expected)) then
134+
err = err:match(".-([^\\/]*)$") -- cut off path of filename
135+
handler('fail', name, msg, "expected errormessage \"" .. err .. "\" to contain \"" .. expected .. "\"")
136+
error('_*_TestAbort_*_')
137+
end
138+
handler('pass', name, msg)
139+
end
140+
141+
local function NTest(testrunname, failoldinterface)
142+
143+
if failoldinterface then error("The interface has changed. Please see documentstion.") end
144+
145+
local pendingtests = {}
146+
local env = _G
147+
local outputhandler = TERMINAL_HANDLER
148+
local started
149+
150+
local function runpending()
151+
if pendingtests[1] ~= nil then
152+
node.task.post(node.task.LOW_PRIORITY, function()
153+
pendingtests[1](runpending)
154+
end)
155+
else
156+
outputhandler('finish', testrunname)
157+
end
158+
end
159+
160+
local function copyenv(dest, src)
161+
dest.eq = src.eq
162+
dest.spy = src.spy
163+
dest.ok = src.ok
164+
dest.nok = src.nok
165+
dest.fail = src.fail
166+
end
167+
168+
local function testimpl(name, f, async)
169+
local testfn = function(next)
170+
171+
local prev = {}
172+
copyenv(prev, env)
173+
174+
local handler = outputhandler
175+
176+
local restore = function(err)
177+
if err then
178+
err = err:match(".-([^\\/]*)$") -- cut off path of filename
179+
if not err:match('_*_TestAbort_*_') then
180+
handler('except', name, err)
181+
end
182+
end
183+
if node then node.setonerror() end
184+
copyenv(env, prev)
185+
outputhandler('end', name)
186+
table.remove(pendingtests, 1)
187+
collectgarbage()
188+
if next then next() end
189+
end
190+
191+
local function wrap(method, ...)
192+
method(handler, name, ...)
193+
end
194+
195+
local function cbError(err)
196+
err = err:match(".-([^\\/]*)$") -- cut off path of filename
197+
if not err:match('_*_TestAbort_*_') then
198+
handler('except', name, err)
199+
end
200+
restore()
201+
end
202+
203+
env.eq = deepeq
204+
env.spy = spy
205+
env.ok = function (cond, msg1, msg2) wrap(assertok, false, cond, msg1, msg2) end
206+
env.nok = function(cond, msg1, msg2) wrap(assertok, true, cond, msg1, msg2) end
207+
env.fail = function (func, expected, msg) wrap(fail, func, expected, msg) end
208+
209+
handler('begin', name)
210+
node.setonerror(cbError)
211+
local ok, err = pcall(f, async and restore)
212+
if not ok then
213+
err = err:match(".-([^\\/]*)$") -- cut off path of filename
214+
if not err:match('_*_TestAbort_*_') then
215+
handler('except', name, err)
216+
end
217+
if async then
218+
restore()
219+
end
220+
end
221+
222+
if not async then
223+
restore()
224+
end
225+
end
226+
227+
if not started then
228+
outputhandler('start', testrunname)
229+
started = true
230+
end
231+
232+
233+
table.insert(pendingtests, testfn)
234+
if #pendingtests == 1 then
235+
runpending()
236+
end
237+
end
238+
239+
local function test(name, f)
240+
testimpl(name, f)
241+
end
242+
243+
local function testasync(name, f)
244+
testimpl(name, f, true)
245+
end
246+
247+
local function report(f, envP)
248+
outputhandler = f or outputhandler
249+
env = envP or env
250+
end
251+
252+
local currentCoName
253+
254+
local function testco(name, func)
255+
-- local t = tmr.create();
256+
local co
257+
testasync(name, function(Next)
258+
currentCoName = name
259+
260+
local function getCB(cbName)
261+
return function(...) -- upval: co, cbName
262+
local result, err = coroutine.resume(co, cbName, ...)
263+
if (not result) then
264+
if (name == currentCoName) then
265+
currentCoName = nil
266+
Next(err)
267+
else
268+
outputhandler('fail', name, "Found stray Callback '"..cbName.."' from test '"..name.."'")
269+
end
270+
elseif coroutine.status(co) == "dead" then
271+
currentCoName = nil
272+
Next()
273+
end
274+
end
275+
end
276+
277+
local function waitCb()
278+
return coroutine.yield()
279+
end
280+
281+
co = coroutine.create(function(wr, wa)
282+
func(wr, wa)
283+
end)
284+
285+
local result, err = coroutine.resume(co, getCB, waitCb)
286+
if (not result) then
287+
currentCoName = nil
288+
Next(err)
289+
elseif coroutine.status(co) == "dead" then
290+
currentCoName = nil
291+
Next()
292+
end
293+
end)
294+
end
295+
296+
297+
return {test = test, testasync = testasync, testco = testco, report = report}
298+
end
299+
300+
return NTest
301+

0 commit comments

Comments
 (0)