瀏覽代碼

Create NodeMCU test system based on gambiarra (#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
Gregor Hartmann 3 年之前
父節點
當前提交
c4baa9f3b9

+ 5 - 2
.travis.yml

@@ -32,7 +32,10 @@ script:
 - if [ "$OS" = "linux" -a "$TRAVIS_PULL_REQUEST" != "false" ]; then bash "$TRAVIS_BUILD_DIR"/tools/travis/pr-build.sh; fi
 - cd "$TRAVIS_BUILD_DIR"
 - echo "checking:"
-- find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 echo
-- find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 $LUACC -p
+- find lua_modules lua_examples tests/NTest* -iname "*.lua" -print0 | xargs -0 echo
+- find lua_modules lua_examples tests/NTest* -iname "*.lua" -print0 | xargs -0 $LUACC -p
+- cd tests/NTest
+- if [ "$OS" = "linux" ]; then ../../$LUACC -e ../NTest/NTest_NTest.lua; fi
+- cd "$TRAVIS_BUILD_DIR"
 - if [ "$OS" = "linux" ]; then bash "$TRAVIS_BUILD_DIR"/tools/travis/run-luacheck-linux.sh; fi
 - if [ "$OS" = "windows" ]; then bash "$TRAVIS_BUILD_DIR"/tools/travis/run-luacheck-windows.sh; fi

+ 301 - 0
tests/NTest/NTest.lua

@@ -0,0 +1,301 @@
+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
+
+--[[
+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 function NTest(testrunname, failoldinterface)
+
+  if failoldinterface then error("The interface has changed. Please see documentstion.") end
+
+  local pendingtests = {}
+  local env = _G
+  local outputhandler = TERMINAL_HANDLER
+  local started
+
+  local function runpending()
+    if pendingtests[1] ~= nil then
+      node.task.post(node.task.LOW_PRIORITY, function()
+        pendingtests[1](runpending)
+      end)
+    else
+      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, env)
+
+      local handler = 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(env, prev)
+        outputhandler('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
+
+      env.eq = deepeq
+      env.spy = spy
+      env.ok = function (cond, msg1, msg2) wrap(assertok, false, cond, msg1, msg2) end
+      env.nok = function(cond, msg1, msg2) wrap(assertok, true,  cond, msg1, msg2) 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
+      outputhandler('start', testrunname)
+      started = true
+    end
+
+
+    table.insert(pendingtests, testfn)
+    if #pendingtests == 1 then
+      runpending()
+    end
+  end
+
+  local function test(name, f)
+    testimpl(name, f)
+  end
+
+  local function testasync(name, f)
+    testimpl(name, f, true)
+  end
+
+  local function report(f, envP)
+    outputhandler = f or outputhandler
+    env = envP or env
+  end
+
+  local currentCoName
+
+  local function testco(name, func)
+  --  local t = tmr.create();
+    local co
+    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
+                      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 {test = test, testasync = testasync, testco = testco, report = report}
+end
+
+return NTest
+

+ 203 - 0
tests/NTest/NTest.md

@@ -0,0 +1,203 @@
+# NTest
+| Since  | Origin / Contributor  | Maintainer  | Source  |
+| :----- | :-------------------- | :---------- | :------ |
+| 2020-11-01 | [Gregor Hartmann](https://github.com/HHHartmann) | [Gregor Hartmann](https://github.com/HHHartmann) | [NTest.lua](NTest.lua) |
+
+NTest is a test system for NodeMCU which is originally based on gambiarra. It is designed to run on chip but the selftest also runs on the host using luac.cross.
+
+!!! warning
+	This module is too big to load by standard `require` function or compile on ESP8266 using `node.compile()`. The only option to load and use it is to use [LFS](../lfs.md).
+
+## Example
+
+``` Lua
+-- Simple synchronous test
+local tests = require("NTest")("first testrun")
+
+tests.test('Check dogma', function()
+  ok(2+2 == 5, 'two plus two equals five')
+end)
+
+-- A more advanced asynchronous test
+tests.testasync('Do it later', function(done)
+  someAsyncFn(function(result)
+    ok(result == expected)
+    done()     -- this starts the next async test
+  end)
+end)
+
+-- An asynchronous test using a coroutine
+tests.testco('Do it in place', function(getCB, waitCB)
+  someAsyncFn(getCB("callback"))
+  local CBName = waitCB()
+  ok(CBName, "callback")
+end)
+```
+
+## API
+
+`require('NTest')`  returns an new function which must be called with a string.
+
+``` Lua
+local new = require('NTest')
+```
+
+`new(testrunname:string)`       returns an object with the following functions:
+
+``` Lua
+local tests = new("first testrun")
+```
+
+`test(name:string, f:function)` allows you to define a new test:
+
+``` Lua
+tests.test('My sync test', function()
+end)
+```
+
+`testasync(name:string, f:function(done:function))` allows you to define a new asynchronous test:
+To tell NTest that the test is finished you need to call the function `done` which gets passed in.
+In async scenarios the test function will usually terminate quickly but the test is still waiting for
+some callback to be called before it is finished.
+
+``` Lua
+tests.testasync('My async test', function(done)
+  done()
+end)
+```
+
+`testco(name:string, f:function(getCB:function, waitCB:function))` allows you to define a new asynchronous
+test which runs in a coroutine:
+This allows handling callbacks in the test in a linear way. simply use getCB to create a new callback stub.
+waitCB blocks the test until the callback is called and returns the parameters.
+
+``` Lua
+tests.testco('My coroutine test', function(getCB, waitCB)
+end)
+
+tests.testco('My coroutine test with callback', function(getCB, waitCB)
+  local t = tmr.create();
+  t:alarm(200, tmr.ALARM_AUTO, getCB("timer"))
+  local name, tCB = waitCB()
+  ok(eq(name, "timer"), "CB name matches")
+  
+  name, tCB = waitCB()
+  ok(eq(name, "timer"), "CB name matches again")
+
+  tCB:stop()
+  
+  ok(true, "coroutine end")
+end)
+
+```
+
+All test functions also define some helper functions that are added when the test is executed - `ok`, `nok`, `fail`, `eq` and `spy`.
+
+`ok`, `nok`, `fail` are assert functions which will break the test if the condition is not met.
+
+`ok(cond:bool, [msg:string])`. It takes any boolean condition and an optional assertion message. If no message is defined - current filename and line will be used. If the condition evaluetes to thuthy nothing happens.
+If it is falsy the message is printed and the test is aborted. The next test will then be executed.
+
+``` Lua
+ok(1 == 2)                   -- prints 'foo.lua:42'
+ok(1 == 2, 'one equals one') -- prints 'one equals one'
+ok(1 == 1)                   -- prints nothing
+```
+
+`nok(cond:bool, [msg:string])` is a shorthand for `ok(not cond, 'msg')`.
+
+``` Lua
+nok(1 == 1)                   -- prints 'foo.lua:42'
+nok(1 == 1, 'one equals one') -- prints 'one equals one'
+```
+
+`fail(func:function, [expected:string], [msg:string])` tests a function for failure. If expected is given the errormessage poduced by the function must also contain the given string else `fail` will fail the test. If no message is defined the current filename and line will be used.
+
+``` Lua
+local function f() error("my error") end
+fail(f, "expected error", "Failed with incorrect error")
+     -- fails with 'Failed with incorrect error' and 
+     --            'expected errormessage "foo.lua:2: my error" to contain "expected error"'
+``` 
+
+`eq(a, b)` is a helper to deeply compare lua variables. It supports numbers, strings, booleans, nils, functions and tables. It's mostly useful within ok() and nok():
+If the variables are equal it returns `true` else it returns `{msg='<reason>'}` This is recognized by `ok` and `nok` and results in also logging the reason for difference.
+
+``` Lua
+ok(eq(1, 1))
+ok(eq({a='b',c='d'}, {c='d',a='b'})
+ok(eq('foo', 'bar'))     -- will fail
+```
+
+`spy([f])` creates function wrappers that remember each call (arguments, errors) but behaves much like the real function. Real function is optional, in this case spy will return nil, but will still record its calls.
+Spies are most helpful when passing them as callbacks and testing that they were called with correct values.
+
+``` Lua
+local f = spy(function(s) return #s end)
+ok(f('hello') == 5)
+ok(f('foo') == 3)
+ok(#f.called == 2)
+ok(eq(f.called[1], {'hello'})
+ok(eq(f.called[2], {'foo'})
+f(nil)
+ok(f.errors[3] ~= nil)
+```
+
+## Reports
+
+Another useful feature is that you can customize test reports as you need. The default `reports` just more or less prints out a basic report. You can easily override this behavior as well as add any other information you need (number of passed/failed assertions, time the test took etc):
+
+Events are:
+`start`   when testing starts
+`finish`  when all tests have finished
+`begin`   Will be called before each test
+`end`     Will be called after each test
+`pass`    Test has passed
+`fail`    Test has failed with not fulfilled assert (ok, nok, fail)
+`except`  Test has failed with unexpected error
+
+
+``` Lua
+local passed = 0
+local failed = 0
+
+tests.report(function(event, testfunc, msg)
+  if event == 'begin' then
+    print('Started test', testfunc)
+    passed = 0
+    failed = 0
+  elseif event == 'end' then
+    print('Finished test', testfunc, passed, failed)
+  elseif event == 'pass' then
+    passed = passed + 1
+  elseif event == 'fail' then
+    print('FAIL', testfunc, msg)
+    failed = failed + 1
+  elseif event == 'except' then
+    print('ERROR', testfunc, msg)
+  end
+end)
+```
+
+Additionally, you can pass a different environment to keep `_G` unpolluted:
+You need to set it, so the helper functions mentioned above can be added before calling the test function.
+
+``` Lua
+local myenv = {}
+
+tests.report(function() ... end, myenv)
+
+tests.test('Some test', function()
+  myenv.ok(myenv.eq(...))
+  local f = myenv.spy()
+end)
+```
+
+You can set any of the parameters to `nil` to leave the value unchanged. 
+
+
+## Appendix
+
+This Library is for NodeMCU versions Lua 5.1 and Lua 5.3. 
+
+It is based on https://bitbucket.org/zserge/gambiarra and includes bugfixes, substantial extensions of functionality and adaptions to NodeMCU requirements.

+ 563 - 0
tests/NTest/NTest_NTest.lua

@@ -0,0 +1,563 @@
+local N = require('NTest')("selftest")
+local orig_node = node
+local metatest
+local async
+local expected = {}
+local failed, passed = 0,0
+local load_tests2
+local function load_tests()
+  --
+  -- Basic tests
+  --
+  metatest('simple assert passes', function()
+    ok(2 == 2, '2==2')
+  end, {'2==2'}, {})
+
+  metatest('simple negative assert fails', function()
+    nok(2 == 2, '2==2')
+    nok(false, 'unreachable code')
+  end, {}, {'2==2'})
+
+  metatest('simple assert fails', function()
+    ok(1 == 2, '1~=2')
+    ok(true, 'unreachable code')
+  end, {}, {'1~=2'})
+
+  metatest('simple negative assert passes', function()
+    nok(1 == 2, '1~=2')
+  end, {'1~=2'}, {})
+
+  metatest('ok without a message', function()
+    ok(1 == 1)
+    ok(1 == 2)
+  end, {'NTest_NTest.lua:31'}, {'NTest_NTest.lua:32'})
+
+  metatest('nok without a message', function()
+    nok(1 == "")
+    nok(1 == 1)
+  end, {'NTest_NTest.lua:36'}, {'NTest_NTest.lua:37'})
+
+  --
+  -- Equality tests
+  --
+  metatest('eq nil', function()
+    ok(eq(nil, nil), 'nil == nil')
+    nok(eq("", nil), '"" != nil')
+    nok(eq(nil, ""), 'nil != ""')
+  end, {'nil == nil', '"" != nil', 'nil != ""'}, {})
+
+  metatest('eq primitives', function()
+    ok(eq('foo', 'foo'), 'str == str')
+    nok(eq('foo', 'bar'), 'str != str')
+    ok(eq(123, 123), 'num == num')
+    nok(eq(123, 12), 'num != num')
+  end, {'str == str', 'str != str', 'num == num', 'num != num'}, {})
+
+  metatest('eq arrays', function()
+    ok(eq({}, {}), 'empty')
+    ok(eq({1, 2}, {1, 2}), 'equal')
+    nok(eq({1, 2}, {2, 1}), 'swapped')
+    nok(eq({1, 2, 3}, {1, 2}), 'longer')
+    nok(eq({1, 2}, {1, 2, 3}), 'shorter')
+  end, {'empty', 'equal', 'swapped', 'longer', 'shorter'}, {})
+
+  metatest('eq objects', function()
+    ok(eq({}, {}), 'empty')
+    ok(eq({a=1,b=2}, {a=1,b=2}), 'equal')
+    ok(eq({b=2,a=1}, {a=1,b=2}), 'swapped')
+    nok(eq({a=1,b=2}, {a=1,b=3}), 'not equal')
+  end, {'empty', 'equal', 'swapped', 'not equal'}, {})
+
+  metatest('eq nested objects', function()
+    ok(eq({
+      ['1'] = { name = 'mhc', age = 28 },
+      ['2'] = { name = 'arb', age = 26 }
+    }, {
+      ['1'] = { name = 'mhc', age = 28 },
+      ['2'] = { name = 'arb', age = 26 }
+    }), 'equal')
+    ok(eq({
+      ['1'] = { name = 'mhc', age = 28 },
+      ['2'] = { name = 'arb', age = 26 }
+    }, {
+      ['1'] = { name = 'mhc', age = 28 },
+      ['2'] = { name = 'arb', age = 27 }
+    }), 'not equal')
+  end, {'equal'}, {'not equal', 'different numbers expected 26 vs. 27'})
+
+  metatest('eq functions', function()
+    ok(eq(function(x) return x end, function(x) return x end), 'equal')
+    nok(eq(function(z) return x + z end, function(z) return y + z end), 'wrong variable') -- luacheck: ignore
+    nok(eq(function(x) return x*2 end, function(x) return x+2 end), 'wrong code')
+  end, {'equal', 'wrong variable', 'wrong code'}, {})
+
+  metatest('eq different types', function()
+    local eqos = eq({a=1,b=2}, "text")
+    ok(eq(eqos.msg, "type 1 is table, type 2 is string"), 'object/string')
+    local eqfn = eq(function(x) return x end, 12)
+    ok(eq(eqfn.msg, "type 1 is function, type 2 is number"), 'function/int')
+    nok(eq(12, "Hallo"), 'int/string')
+  end, {"object/string", "function/int", 'int/string'}, {})
+
+  --
+  -- Spies
+  --
+  metatest('spy called', function()
+    local f = spy()
+    ok(not f.called or #f.called == 0, 'not called')
+    f()
+    ok(f.called, 'called')
+    ok(#f.called == 1, 'called once')
+    f()
+    ok(#f.called == 2, 'called twice')
+  end, {'not called', 'called', 'called once', 'called twice'}, {})
+
+  metatest('spy with arguments', function()
+    local x = 0
+    local function setX(n) x = n end
+    local f = spy(setX)
+    f(1)
+    ok(x == 1, 'x == 1')
+    ok(eq(f.called, {{1}}), 'called with 1')
+    f(42)
+    ok(x == 42, 'x == 42')
+    ok(eq(f.called, {{1}, {42}}), 'called with 42')
+  end, {'x == 1', 'called with 1', 'x == 42', 'called with 42'}, {})
+
+  metatest('spy with nils', function()
+    local function nils(a, _, b) return a, nil, b, nil end
+    local f = spy(nils)
+    local r1, r2, r3, r4 = f(1, nil, 2)
+    ok(eq(f.called, {{1, nil, 2}}), 'nil in args')
+    ok(r1 == 1 and r2 == nil and r3 == 2 and r4 == nil, 'nil in returns')
+  end, {'nil in args', 'nil in returns'}, {})
+
+  metatest('spy with exception', function()
+    local function throwSomething(s)
+      if s ~= 'nopanic' then error('panic: '..s) end
+    end
+    local f = spy(throwSomething)
+    f('nopanic')
+    ok(f.errors == nil, 'no errors yet')
+    f('foo')
+    ok(eq(f.called, {{'nopanic'}, {'foo'}}), 'args ok')
+    ok(f.errors[1] == nil and f.errors[2] ~= nil, 'thrown ok')
+  end, {'no errors yet', 'args ok', 'thrown ok'}, {})
+
+  metatest('another spy with exception', function()
+    local f = spy(function() local a = unknownVariable + 1 end) -- luacheck: ignore
+    f()
+    ok(f.errors[1], 'exception thrown')
+  end, {'exception thrown'}, {})
+
+  metatest('spy should return a value', function()
+    local f = spy(function() return 5 end)
+    ok(f() == 5, 'spy returns a value')
+    local g = spy()
+    ok(g() == nil, 'default spy returns undefined')
+  end, {'spy returns a value', 'default spy returns undefined'}, {})
+
+  --
+  -- fail tests
+  --
+  metatest('fail with correct errormessage', function()
+    fail(function() error("my error") end, "my error", "Failed with correct error")
+    ok(true, 'reachable code')
+  end, {'Failed with correct error', 'reachable code'}, {})
+
+  metatest('fail with incorrect errormessage', function()
+    fail(function() error("my error") end, "different error", "Failed with incorrect error")
+    ok(true, 'unreachable code')
+  end, {}, {'Failed with incorrect error',
+        'expected errormessage "NTest_NTest.lua:169: my error" to contain "different error"'})
+
+  metatest('fail with incorrect errormessage default message', function()
+    fail(function() error("my error") end, "different error")
+    ok(true, 'unreachable code')
+  end, {}, {'NTest_NTest.lua:175',
+        'expected errormessage "NTest_NTest.lua:175: my error" to contain "different error"'})
+
+  metatest('fail with not failing code', function()
+    fail(function() end, "my error", "did not fail")
+    ok(true, 'unreachable code')
+  end, {}, {"did not fail", 'Expected to fail with Error containing "my error"'})
+
+  metatest('fail with failing code', function()
+    fail(function() error("my error") end, nil, "Failed as expected")
+    ok(true, 'reachable code')
+  end, {'Failed as expected', 'reachable code'}, {})
+
+  metatest('fail with not failing code', function()
+    fail(function() end, nil , "did not fail")
+    ok(true, 'unreachable code')
+  end, {}, {"did not fail", 'Expected to fail with Error'})
+
+  metatest('fail with not failing code default message', function()
+    fail(function() end)
+    ok(true, 'unreachable code')
+  end, {}, {"NTest_NTest.lua:196", 'Expected to fail with Error'})
+
+  metatest('=== load more tests ===', function()
+    load_tests2()
+  end, {}, {})
+
+end
+
+load_tests2 = function()
+  --
+  -- except tests
+  --
+  metatest('error should panic', function()
+    error("lua error")
+    ok(true, 'unreachable code')
+  end, {}, {}, {'NTest_NTest.lua:211: lua error'})
+
+  --
+  -- called function except
+  --
+
+  local function subfuncerror()
+    error("lua error")
+  end
+
+  metatest('subroutine error should panic', function()
+    subfuncerror()
+    ok(true, 'unreachable code')
+  end, {}, {}, {'NTest_NTest.lua:220: lua error'})
+
+  local function subfuncok()
+    ok(false)
+  end
+
+  metatest('subroutine ok should fail', function()
+    subfuncok()
+    ok(true, 'unreachable code')
+  end, {}, {'NTest_NTest.lua:229'})
+
+  --drain_post_queue()
+
+  --
+  -- Async tests
+  --
+  metatest('async test', function(next)
+    async(function()
+      ok(true, 'bar')
+      async(function()
+        ok(true, 'baz')
+        next()
+      end)
+    end)
+    ok(true, 'foo')
+  end, {'foo', 'bar', 'baz'}, {}, {}, true)
+
+  metatest('async test without actually async', function(next)
+    ok(true, 'bar')
+    next()
+  end, {'bar'}, {}, {}, true)
+
+  metatest('async fail in main', function(--[[ next ]])
+    ok(false, "async fail")
+    ok(true, 'unreachable code')
+  end, {}, {'async fail'}, {}, true)
+
+  --drain_post_queue()
+
+  metatest('another async test after async queue drained', function(next)
+    async(function()
+      ok(true, 'bar')
+      next()
+    end)
+    ok(true, 'foo')
+  end, {'foo', 'bar'}, {}, {}, true)
+
+  --
+  -- except tests async
+  --
+  metatest('async except in main', function(--[[ next ]])
+    error("async except")
+    ok(true, 'unreachable code')
+  end, {}, {}, {'NTest_NTest.lua:277: async except'}, true)
+
+  metatest('async fail in callback', function(next)
+    async(function()
+      ok(false, "async fail")
+      next()
+    end)
+    ok(true, 'foo')
+  end, {'foo'}, {'async fail'}, {}, true)
+
+  metatest('async except in callback', function(next)
+    async(function()
+      error("async Lua error")
+      next()
+    end)
+    ok(true, 'foo')
+  end, {'foo'}, {}, {'NTest_NTest.lua:291: async Lua error'}, true)
+
+  --
+  -- sync after async test
+  --
+  local marker = 0
+  metatest('set marker async', function(next)
+    async(function()
+      marker = "marked"
+      ok(true, 'async bar')
+      next()
+    end)
+    ok(true, 'foo')
+  end, {'foo', 'async bar'}, {}, {}, true)
+
+  metatest('check marker async', function()
+    ok(eq(marker, "marked"), "marker check")
+  end, {"marker check"}, {})
+
+  --
+  -- coroutine async tests
+  --
+  metatest('coroutine pass', function(--[[ getCB, waitCB ]])
+    ok(true, "passing")
+  end, {"passing"}, {}, {}, "co")
+
+  metatest('coroutine fail in main', function(--[[ getCB, waitCB ]])
+    ok(false, "coroutine fail")
+    ok(true, 'unreachable code')
+  end, {}, {'coroutine fail'}, {}, "co")
+
+  metatest('coroutine fail in main', function(--[[ getCB, waitCB ]])
+    nok(true, "coroutine fail")
+    nok(false, 'unreachable code')
+  end, {}, {'coroutine fail'}, {}, "co")
+
+  metatest('coroutine fail error', function(--[[ getCB, waitCB ]])
+    fail(function() error("my error") end, "my error", "Failed with correct error")
+    fail(function() error("my error") end, "other error", "Failed with other error")
+    ok(true, 'unreachable code')
+  end, {'Failed with correct error'}, {'Failed with other error',
+        'expected errormessage "NTest_NTest.lua:333: my error" to contain "other error"'}, {}, "co")
+
+  metatest('coroutine except in main', function(--[[ getCB, waitCB ]])
+    error("coroutine except")
+    ok(true, 'unreachable code')
+  end, {}, {}, {'NTest_NTest.lua:339: coroutine except'}, "co")
+
+  --local function coasync(f) table.insert(post_queue, 1, f) end
+  local function coasync(f, p1, p2) node.task.post(node.task.MEDIUM_PRIORITY, function() f(p1,p2) end) end
+
+  metatest('coroutine with callback', function(getCB, waitCB)
+    coasync(getCB("testCb"))
+    local name = waitCB()
+    ok(eq(name, "testCb"), "cb")
+  end, {"cb"}, {}, {}, "co")
+
+  metatest('coroutine with callback with values', function(getCB, waitCB)
+    coasync(getCB("testCb"), "p1", 2)
+    local name, p1, p2 = waitCB()
+    ok(eq(name, "testCb"), "cb")
+    ok(eq(p1, "p1"), "p1")
+    ok(eq(p2, 2), "p2")
+  end, {"cb", "p1", "p2"}, {}, {}, "co")
+
+  metatest('coroutine fail after callback', function(getCB, waitCB)
+    coasync(getCB("testCb"))
+    local name = waitCB()
+    ok(eq(name, "testCb"), "cb")
+    ok(false, "fail")
+    ok(true, 'unreachable code')
+  end, {"cb"}, {"fail"}, {}, "co")
+
+  metatest('coroutine except after callback', function(getCB, waitCB)
+    coasync(getCB("testCb"))
+    local name = waitCB()
+    ok(eq(name, "testCb"), "cb")
+    error("error")
+    ok(true, 'unreachable code')
+  end, {"cb"}, {}, {"NTest_NTest.lua:372: error"}, "co")
+
+  --- detect stray callbacks after the test has finished
+  local strayCb
+  local function rewrap()
+    coasync(strayCb)
+  end
+
+  metatest('leave stray callback', function(getCB--[[ , waitCB ]])
+    strayCb = getCB("testa")
+    coasync(rewrap)
+  end, {}, {}, {}, "co")
+
+  metatest('test after stray cb', function(--[[ getCB, waitCB ]])
+  end, {}, {"Found stray Callback 'testa' from test 'leave stray callback'"}, {}, "co")
+
+
+
+  --
+  -- Finalize: check test results
+  --
+  metatest("finishing up pending tests", function()
+    for i = 1,#expected -1 do
+      print("--- FAIL "..expected[i].name..' (pending)')
+      failed = failed + 1
+    end
+    print("failed: "..failed, "passed: "..passed)
+    node = orig_node -- luacheck: ignore 121 (setting read-only global variable)
+  end, {}, {})
+
+end -- load_tests()
+
+
+local cbWrap = function(cb) return cb end
+
+-- implement pseudo task handling for on host testing
+local drain_post_queue = function() end
+
+if not node then
+  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
+
+  local errorfunc
+  node.setonerror = function(fn) errorfunc = fn end
+  -- luacheck: pop
+
+  cbWrap = function(cb)
+      return function(...)
+          local ok, p1,p2,p3,p4 = pcall(cb, ...)
+          if not ok then
+            if errorfunc then
+              errorfunc(p1)
+            else
+              print(p1, "::::::::::::: reboot :::::::::::::")
+            end
+          else
+            return p1,p2,p3,p4
+          end
+        end
+    end
+end
+
+-- Helper function to print arrays
+local function stringify(t)
+  local s = ''
+  for i=1,#(t or {}) do
+    s = s .. '"' .. t[i] .. '"' .. ', '
+  end
+  return s:gsub('..$', '')
+end
+
+local pass
+-- Set meta test handler
+N.report(function(e, test, msg, errormsg)
+  local function consumemsg(msg, area) -- luacheck: ignore  
+    if not expected[1][area][1] then
+      print("--- FAIL "..expected[1].name..' ('..area..'ed): unexpected "'..
+        msg..'"')
+      pass = false
+      return
+    end
+
+    if msg ~= expected[1][area][1] then
+      print("--- FAIL "..expected[1].name..' ('..area..'ed): expected "'..
+        expected[1][area][1]..'" vs "'..
+        msg..'"')
+      pass = false
+    end
+    table.remove(expected[1][area], 1)
+  end
+
+  local function consumeremainder(area)
+    if #expected[1][area] > 0 then
+      print("--- FAIL "..expected[1].name..' ('..area..'ed): expected ['..
+        stringify(expected[1][area])..']')
+      pass = false
+    end
+  end
+
+  if e == 'begin' then
+    pass = true
+  elseif e == 'end' then
+    consumeremainder("pass")
+    consumeremainder("fail")
+    consumeremainder("except")
+    if pass then
+      print("+++ Pass "..expected[1].name)
+      passed = passed + 1
+    else
+      failed = failed + 1
+    end
+    table.remove(expected, 1)
+  elseif e == 'pass' then
+    consumemsg(msg, "pass")
+    if errormsg then consumemsg(errormsg, "pass") end
+  elseif e == 'fail' then
+    consumemsg(msg, "fail")
+    if errormsg then consumemsg(errormsg, "fail") end
+  elseif e == 'except' then
+    consumemsg(msg, "except")
+    if errormsg then consumemsg(errormsg, "except") end
+  elseif e == 'start' or e == 'finish' then -- luacheck: ignore
+    -- ignore
+  else
+    print("Extra output: ", e, test, msg, errormsg)
+  end
+end)
+
+local async_queue = {}
+async = function(f) table.insert(async_queue, cbWrap(f)) end
+local function async_next()
+  local f = table.remove(async_queue, 1)
+  if f then
+    f()
+  end
+end
+
+local function drain_async_queue()
+  while #async_queue > 0 do
+    async_next()
+  end
+end
+
+metatest = function(name, f, expectedPassed, expectedFailed, expectedExcept, asyncMode)
+  local ff = f
+  if asyncMode then
+    ff = function(...)
+      f(...)
+      drain_async_queue()
+    end
+    if (asyncMode == "co") then
+      N.testco(name,ff)
+    else
+      N.testasync(name, ff)
+    end
+  else
+    N.test(name, ff)
+  end
+  table.insert(expected, {
+    name = name,
+    pass = expectedPassed,
+    fail = expectedFailed,
+    except = expectedExcept or {}
+  })
+end
+
+
+load_tests()
+
+drain_post_queue()

+ 239 - 0
tests/NTest_file.lua

@@ -0,0 +1,239 @@
+local N = require('NTest')("file")
+
+local function cleanup()
+    file.remove("testfile")
+    file.remove("testfile2")
+    local testfiles = {"testfile1&", "testFILE2"}
+    for _, n in ipairs(testfiles) do
+      file.remove(n,n)
+    end
+end
+
+N.test('exist', function()
+  cleanup()
+  nok(file.exists("non existing file"), "non existing file")
+
+  file.putcontents("testfile", "testcontents")
+  ok(file.exists("testfile"), "existing file")
+end)
+
+
+N.test('fscfg', function()
+  cleanup()
+  local start, size = file.fscfg()
+  ok(start, "start")
+  ok(size, "size")
+end)
+
+N.test('fsinfo', function()
+  cleanup()
+  local remaining, used, total = file.fsinfo()
+  ok(remaining, "remaining")
+  ok(used, "used")
+  ok(total, "total")
+  ok(eq(remaining+used, total), "size maths")
+end)
+
+N.test('getcontents', function()
+  cleanup()
+  local testcontent = "some content \0 and more"
+  file.putcontents("testfile", testcontent)
+  local content = file.getcontents("testfile")
+  ok(eq(testcontent, content),"contents")
+end)
+
+N.test('getcontents non existent file', function()
+  cleanup()
+  nok(file.getcontents("non existing file"), "non existent file")
+end)
+
+N.test('getcontents more than 1K', function()
+  cleanup()
+  local f = file.open("testfile", "w")
+  for i = 1,100 do -- luacheck: ignore
+    f:write("some text to test")
+  end
+  f:close()
+  local content = file.getcontents("testfile")
+  ok(eq(#content, 1700), "long contents")
+end)
+
+N.test('read more than 1K', function()
+  cleanup()
+  local f = file.open("testfile", "w")
+  for i = 1,100 do -- luacheck: ignore
+    f:write("some text to test")
+  end
+  f:close()
+  f = file.open("testfile","r")
+  local buffer = f:read()
+  ok(eq(#buffer, 1024), "first block")
+  buffer = f:read()
+  f:close()
+  ok(eq(#buffer, 1700-1024), "second block")
+end)
+
+local function makefile(name, num128)
+  local s128 = "16 bytes written"
+  s128 = s128..s128..s128..s128
+  s128 = s128..s128
+  local f = file.open(name, "w")
+  for i = 1, num128 do -- luacheck: ignore
+    f:write(s128)
+  end
+  f:close()
+end
+
+N.test('read 7*128 bytes', function()
+  cleanup()
+  makefile("testfile", 7)
+  local f = file.open("testfile","r")
+  local buffer = f:read()
+  f:close()
+  nok(eq(buffer, nil), "nil first block")
+  ok(eq(#buffer, 128*7), "length first block")
+end)
+
+N.test('read 8*128 bytes long file', function()
+  cleanup()
+  makefile("testfile", 8)
+  local f = file.open("testfile","r")
+  local buffer = f:read()
+  nok(eq(buffer, nil), "nil first block")
+  ok(eq(#buffer, 128*8), "size first block")
+  buffer = f:read()
+  f:close()
+  ok(eq(buffer, nil), "nil second block")
+end)
+
+N.test('read 9*128 bytes', function()
+  cleanup()
+  makefile("testfile", 9)
+  local f = file.open("testfile","r")
+  local buffer = f:read()
+  nok(eq(buffer, nil), "nil first block")
+  ok(eq(#buffer, 1024), "size first block")
+  buffer = f:read()
+  f:close()
+  nok(eq(buffer, nil), "nil second block")
+  ok(eq(#buffer, 128*9-1024), "size second block")
+end)
+
+N.test('list', function()
+  cleanup()
+
+  local function count(files)
+    local filecount = 0
+    for _,_ in pairs(files) do filecount = filecount+1 end
+    return filecount
+  end
+
+  local files
+  local function testfile(name)
+    ok(eq(files[name],#name), "length matches name length")
+  end
+
+  local testfiles = {"testfile1&", "testFILE2"}
+  for _, n in ipairs(testfiles) do
+    file.putcontents(n,n)
+  end
+
+  files = file.list("testfile%.*")
+  ok(eq(count(files), 1), "found file")
+  testfile("testfile1&")
+
+  files = file.list("^%l*%u+%d%.-")
+  ok(eq(count(files), 1), "found file regexp")
+  testfile("testFILE2")
+
+  files = file.list()
+  ok(count(files) >= 2, "several files found")
+end)
+
+N.test('open non existing', function()
+  cleanup()
+  local function testopen(test, filename, mode)
+    test(file.open(filename, mode), mode)
+    file.close()
+    file.remove(filename)
+  end
+
+  testopen(nok, "testfile", "r")
+  testopen(ok, "testfile", "w")
+  testopen(ok, "testfile", "a")
+  testopen(nok, "testfile", "r+")
+  testopen(ok, "testfile", "w+")
+  testopen(ok, "testfile", "a+")
+end)
+
+N.test('open existing', function()
+  cleanup()
+  local function testopen(mode, position)
+    file.putcontents("testfile", "testcontent")
+    ok(file.open("testfile", mode), mode)
+    file.write("")
+    ok(eq(file.seek(), position), "seek check")
+    file.close()
+  end
+
+  testopen("r", 0)
+  testopen("w", 0)
+  testopen("a", 11)
+  testopen("r+", 0)
+  testopen("w+", 0)
+  testopen("a+", 11)
+end)
+
+N.test('remove', function()
+  cleanup()
+  file.putcontents("testfile", "testfile")
+
+  ok(file.remove("testfile") == nil, "existing file")
+  ok(file.remove("testfile") == nil, "non existing file")
+end)
+
+N.test('rename', function()
+  cleanup()
+  file.putcontents("testfile", "testfile")
+
+  ok(file.rename("testfile", "testfile2"), "rename existing")
+  nok(file.exists("testfile"), "old file removed")
+  ok(file.exists("testfile2"), "new file exists")
+
+  nok(file.rename("testfile", "testfile3"), "rename non existing")
+
+  file.putcontents("testfile", "testfile")
+
+  nok(file.rename("testfile", "testfile2"), "rename to existing")
+  ok(file.exists("testfile"), "from file exists")
+  ok(file.exists("testfile2"), "to file exists")
+end)
+
+N.test('stat existing file', function()
+  cleanup()
+  file.putcontents("testfile", "testfile")
+
+  local stat = file.stat("testfile")
+  ok(stat, "stat existing")
+  ok(eq(stat.size, 8), "size")
+  ok(eq(stat.name, "testfile"), "name")
+  ok(stat.time, "no time")
+  ok(eq(stat.time.year, 1970), "year")
+  ok(eq(stat.time.mon, 01), "mon")
+  ok(eq(stat.time.day, 01), "day")
+  ok(eq(stat.time.hour, 0), "hour")
+  ok(eq(stat.time.min, 0), "min")
+  ok(eq(stat.time.sec, 0), "sec")
+  nok(stat.is_dir, "is_dir")
+  nok(stat.is_rdonly, "is_rdonly")
+  nok(stat.is_hidden, "is_hidden")
+  nok(stat.is_sys, "is_sys")
+  nok(stat.is_arch, "is_arch")
+end)
+
+N.test('stat non existing file', function()
+  cleanup()
+  local stat = file.stat("not existing file")
+
+  ok(stat == nil, "stat empty")
+end)

+ 91 - 0
tests/NTest_tmr.lua

@@ -0,0 +1,91 @@
+local N = require('NTest')("tmr")
+
+N.testasync('SINGLE alarm', function(next)
+  local t = tmr.create();
+  local count = 0
+  t:alarm(200, tmr.ALARM_SINGLE,
+    function()
+      count = count + 1
+      ok(count <= 1, "only 1 invocation")
+      next()
+    end)
+
+  ok(true, "sync end")
+end)
+
+N.testasync('SEMI alarm', function(next)
+  local t = tmr.create();
+  local count = 0
+  t:alarm(200, tmr.ALARM_SEMI,
+    function(tp)
+      count = count + 1
+      if count <= 1 then
+        tp:start()
+        return
+      end
+      ok(eq(count, 2), "only 2 invocations")
+      next()
+    end)
+  ok(true, "sync end")
+end)
+
+N.testasync('AUTO alarm', function(next)
+  local t = tmr.create();
+  local count = 0
+  t:alarm(200, tmr.ALARM_AUTO,
+    function(tp)
+      count = count + 1
+      if count == 2 then
+        tp:stop()
+        return next()
+      end
+      ok(count < 2, "only 2 invocations")
+    end)
+  ok(true, "sync end")
+end)
+
+N.testco('SINGLE alarm coroutine', function(getCB, waitCB)
+  local t = tmr.create();
+  t:alarm(200, tmr.ALARM_SINGLE, getCB("timer"))
+
+  local name, timer = waitCB()
+  ok(eq("timer", name), "CB name matches")
+  ok(eq(t, timer), "CB tmr instance matches")
+
+  ok(true, "coroutine end")
+end)
+
+N.testco('SEMI alarm coroutine', function(getCB, waitCB)
+  local t = tmr.create();
+  t:alarm(200, tmr.ALARM_SEMI, getCB("timer"))
+
+  local name, timer = waitCB()
+  ok(eq("timer", name), "CB name matches")
+  ok(eq(t, timer), "CB tmr instance matches")
+
+  timer:start()
+
+  name, timer = waitCB()
+  ok(eq("timer", name), "CB name matches again")
+  ok(eq(t, timer), "CB tmr instance matches again")
+
+  ok(true, "coroutine end")
+end)
+
+N.testco('AUTO alarm coroutine', function(getCB, waitCB)
+  local t = tmr.create();
+  t:alarm(200, tmr.ALARM_AUTO, getCB("timer"))
+
+  local name, timer = waitCB()
+  ok(eq("timer", name), "CB name matches")
+  ok(eq(t, timer), "CB tmr instance matches")
+
+  name, timer = waitCB()
+  ok(eq("timer", name), "CB name matches again")
+  ok(eq(t, timer), "CB tmr instance matches again")
+
+  timer:stop()
+
+  ok(true, "coroutine end")
+end)
+

+ 11 - 0
tools/luacheck_NTest_config.lua

@@ -0,0 +1,11 @@
+stds.nodemcu_libs = {}
+loadfile ("tools/luacheck_config.lua")(stds)
+local empty = { }
+
+stds.nodemcu_libs.read_globals.ok = empty
+stds.nodemcu_libs.read_globals.nok = empty
+stds.nodemcu_libs.read_globals.eq = empty
+stds.nodemcu_libs.read_globals.fail = empty
+stds.nodemcu_libs.read_globals.spy = empty
+
+std = "lua51+lua53+nodemcu_libs"

+ 4 - 4
tools/luacheck_config.lua

@@ -1,3 +1,4 @@
+stds = stds or ...   -- set stds if this script is called by another config script
 local empty = { }
 local read_write = {read_only = false}
 
@@ -423,6 +424,7 @@ stds.nodemcu_libs = {
         restore = empty,
         setcpufreq = empty,
         setpartitiontable = empty,
+        setonerror = empty,
         sleep = empty,
         stripdebug = empty,
         writercr = empty,
@@ -942,10 +944,8 @@ stds.nodemcu_libs = {
     },
     pack = empty,
     unpack  = empty,
-    size = empty,
-    package = {fields = {seeall = read_write}},
-    _ENV = empty
+    package = {fields = {seeall = read_write}}
   }
 }
 
-std = "lua51+nodemcu_libs"
+std = "lua51+lua53+nodemcu_libs"

+ 4 - 1
tools/travis/run-luacheck-linux.sh

@@ -68,5 +68,8 @@ fi
 
 echo "Static analysys of"
 find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 echo
-
 (find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 luacheck --config tools/luacheck_config.lua) || exit
+
+echo "Static analysys of"
+find tests -iname "*.lua" -print0 | xargs -0 echo
+(find tests -iname "*.lua" -print0 | xargs -0 luacheck --config tools/luacheck_NTest_config.lua) || exit

+ 4 - 1
tools/travis/run-luacheck-windows.sh

@@ -9,5 +9,8 @@ fi
 
 echo "Static analysys of"
 find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 echo
-
 (find lua_modules lua_examples -iname "*.lua" -print0 | xargs -0 cache/luacheck.exe --config tools/luacheck_config.lua) || exit
+
+echo "Static analysys of"
+find tests -iname "*.lua" -print0 | xargs -0 echo
+(find tests -iname "*.lua" -print0 | xargs -0 cache/luacheck.exe --config tools/luacheck_NTest_config.lua) || exit