Consolidate and clean up the test runner logic and associated environment hooks
This commit is contained in:
parent
8cc046fa1b
commit
3283b1d141
@ -1,6 +1,6 @@
|
||||
return {
|
||||
"self-test.lua",
|
||||
"test_declare.lua",
|
||||
"test_hooks.lua",
|
||||
"test_runner.lua",
|
||||
"testfx.lua",
|
||||
}
|
||||
|
@ -43,15 +43,20 @@
|
||||
function m.executeSelfTest()
|
||||
m.loadTestsFromManifests()
|
||||
|
||||
local result, err = m.runTests(_OPTIONS["test-only"])
|
||||
local test, err = m.getTestWithIdentifier(_OPTIONS["test-only"])
|
||||
if err then
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
printf("%d tests passed, %d failed in %0.02f seconds",
|
||||
result.passed, result.failed, result.elapsed)
|
||||
local startTime = os.clock()
|
||||
|
||||
if result.failed > 0 then
|
||||
local passed, failed = m.runTest(test)
|
||||
|
||||
local elapsed = os.clock() - startTime
|
||||
|
||||
printf("%d tests passed, %d failed in %0.02f seconds", passed, failed, elapsed)
|
||||
|
||||
if failed > 0 then
|
||||
os.exit(5)
|
||||
end
|
||||
end
|
||||
@ -84,7 +89,7 @@
|
||||
|
||||
|
||||
dofile("test_declare.lua")
|
||||
dofile("test_hooks.lua")
|
||||
dofile("test_runner.lua")
|
||||
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
|
||||
_.suites = {}
|
||||
_.suppressed = {}
|
||||
|
||||
T = _.suites
|
||||
|
||||
@ -48,6 +49,32 @@
|
||||
|
||||
|
||||
|
||||
---
|
||||
-- Prevent a particular test or suite of tests from running.
|
||||
--
|
||||
-- @param identifier
|
||||
-- A test or suite identifier, indicating which tests should be suppressed,
|
||||
-- in the form "suiteName" or "suiteName.testName".
|
||||
---
|
||||
|
||||
function m.suppress(identifier)
|
||||
if type(identifier) == "table" then
|
||||
for i = 1, #identifier do
|
||||
m.suppress(identifier[i])
|
||||
end
|
||||
else
|
||||
_.suppressed[identifier] = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function m.isSuppressed(identifier)
|
||||
return _.suppressed[identifier]
|
||||
end
|
||||
|
||||
|
||||
|
||||
---
|
||||
-- Returns true if the provided test object represents a valid test.
|
||||
---
|
||||
|
@ -1,77 +0,0 @@
|
||||
---
|
||||
-- test_hooks.lua
|
||||
--
|
||||
-- Wire into the Lua runtime environment to support testing.
|
||||
--
|
||||
-- Author Jason Perkins
|
||||
-- Copyright (c) 2008-2016 Jason Perkins and the Premake project.
|
||||
---
|
||||
|
||||
local p = premake
|
||||
local m = p.modules.self_test
|
||||
|
||||
local _ = {}
|
||||
|
||||
|
||||
function m.installTestingHooks()
|
||||
local hooks = {}
|
||||
|
||||
hooks.io_open = io.open
|
||||
hooks.io_output = io.output
|
||||
hooks.os_writefile_ifnotequal = os.writefile_ifnotequal
|
||||
hooks.p_utf8 = p.utf8
|
||||
hooks.print = print
|
||||
|
||||
io.open = _.stub_io_open
|
||||
io.output = _.stub_io_output
|
||||
os.writefile_ifnotequal = _.stub_os_writefile_ifnotequal
|
||||
print = _.stub_print
|
||||
p.utf8 = _.stub_utf8
|
||||
|
||||
return hooks
|
||||
end
|
||||
|
||||
|
||||
|
||||
function m.removeTestingHooks(hooks)
|
||||
io.open = hooks.io_open
|
||||
io.output = hooks.io_output
|
||||
os.writefile_ifnotequal = hooks.os_writefile_ifnotequal
|
||||
p.utf8 = hooks.p_utf8
|
||||
print = hooks.print
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_io_open(fname, mode)
|
||||
test.value_openedfilename = fname
|
||||
test.value_openedfilemode = mode
|
||||
return {
|
||||
close = function()
|
||||
test.value_closedfile = true
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_io_output(f)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_os_writefile_ifnotequal(content, fname)
|
||||
m.value_openedfilename = fname
|
||||
m.value_closedfile = true
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_print(s)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_utf8()
|
||||
end
|
246
modules/self-test/test_runner.lua
Normal file
246
modules/self-test/test_runner.lua
Normal file
@ -0,0 +1,246 @@
|
||||
---
|
||||
-- self-test/test_runner.lua
|
||||
--
|
||||
-- Execute unit tests and test suites.
|
||||
--
|
||||
-- Author Jason Perkins
|
||||
-- Copyright (c) 2008-2016 Jason Perkins and the Premake project.
|
||||
---
|
||||
|
||||
local p = premake
|
||||
|
||||
local m = p.modules.self_test
|
||||
|
||||
local _ = {}
|
||||
|
||||
|
||||
|
||||
function m.runTest(test)
|
||||
local scopedTestCall
|
||||
|
||||
if test.testFunction then
|
||||
scopedTestCall = _.runTest
|
||||
elseif test.suite then
|
||||
scopedTestCall = _.runTestSuite
|
||||
else
|
||||
scopedTestCall = _.runAllTests
|
||||
end
|
||||
|
||||
return scopedTestCall(test)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.runAllTests()
|
||||
local passed = 0
|
||||
local failed = 0
|
||||
|
||||
local suites = m.getSuites()
|
||||
for suiteName, suite in pairs(suites) do
|
||||
if not m.isSuppressed(suiteName) then
|
||||
local test = {}
|
||||
|
||||
test.suiteName = suiteName
|
||||
test.suite = suite
|
||||
|
||||
local suitePassed, suiteFailed = _.runTestSuite(test)
|
||||
|
||||
passed = passed + suitePassed
|
||||
failed = failed + suiteFailed
|
||||
end
|
||||
end
|
||||
|
||||
return passed, failed
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.runTestSuite(test)
|
||||
local passed = 0
|
||||
local failed = 0
|
||||
|
||||
for testName, testFunction in pairs(test.suite) do
|
||||
test.testName = testName
|
||||
test.testFunction = testFunction
|
||||
|
||||
if m.isValid(test) and not m.isSuppressed(test.suiteName .. "." .. test.testName) then
|
||||
if _.runTest(test) then
|
||||
passed = passed + 1
|
||||
else
|
||||
failed = failed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return passed, failed
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.runTest(test)
|
||||
local hooks = _.installTestingHooks()
|
||||
|
||||
_TESTS_DIR = test.suite._TESTS_DIR
|
||||
_SCRIPT_DIR = test.suite._SCRIPT_DIR
|
||||
|
||||
local ok, err = _.setupTest(test)
|
||||
|
||||
if ok then
|
||||
ok, err = _.executeTest(test)
|
||||
end
|
||||
|
||||
local tok, terr = _.teardownTest(test)
|
||||
ok = ok and tok
|
||||
err = err or terr
|
||||
|
||||
if not ok then
|
||||
m.print(string.format("%s.%s: %s", test.suiteName, test.testName, err))
|
||||
end
|
||||
|
||||
_.removeTestingHooks(hooks)
|
||||
|
||||
return ok, err
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.installTestingHooks()
|
||||
local hooks = {}
|
||||
|
||||
hooks.action = _ACTION
|
||||
hooks.options = _OPTIONS
|
||||
hooks.os = _OS
|
||||
|
||||
hooks.io_open = io.open
|
||||
hooks.io_output = io.output
|
||||
hooks.os_writefile_ifnotequal = os.writefile_ifnotequal
|
||||
hooks.p_utf8 = p.utf8
|
||||
hooks.print = print
|
||||
|
||||
_OPTIONS = {}
|
||||
setmetatable(_OPTIONS, getmetatable(hooks.options))
|
||||
|
||||
io.open = _.stub_io_open
|
||||
io.output = _.stub_io_output
|
||||
os.writefile_ifnotequal = _.stub_os_writefile_ifnotequal
|
||||
print = _.stub_print
|
||||
p.utf8 = _.stub_utf8
|
||||
|
||||
stderr_capture = nil
|
||||
|
||||
p.clearWarnings()
|
||||
p.eol("\n")
|
||||
p.escaper(nil)
|
||||
p.indent("\t")
|
||||
p.api.reset()
|
||||
|
||||
m.value_openedfilename = nil
|
||||
m.value_openedfilemode = nil
|
||||
m.value_closedfile = false
|
||||
|
||||
return hooks
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.removeTestingHooks(hooks)
|
||||
_ACTION = hooks.action
|
||||
_OPTIONS = hooks.options
|
||||
_OS = hooks.os
|
||||
|
||||
io.open = hooks.io_open
|
||||
io.output = hooks.io_output
|
||||
os.writefile_ifnotequal = hooks.os_writefile_ifnotequal
|
||||
p.utf8 = hooks.p_utf8
|
||||
print = hooks.print
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.setupTest(test)
|
||||
if type(test.suite.setup) == "function" then
|
||||
return xpcall(test.suite.setup, _.errorHandler)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.executeTest(test)
|
||||
local result, err
|
||||
p.capture(function()
|
||||
result, err = xpcall(test.testFunction, _.errorHandler)
|
||||
end)
|
||||
return result, err
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.teardownTest(test)
|
||||
if type(test.suite.teardown) == "function" then
|
||||
return xpcall(test.suite.teardown, _.errorHandler)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.errorHandler(err)
|
||||
local msg = err
|
||||
|
||||
-- if the error doesn't include a stack trace, add one
|
||||
if not msg:find("stack traceback:", 1, true) then
|
||||
msg = debug.traceback(err, 2)
|
||||
end
|
||||
|
||||
-- trim of the trailing context of the originating xpcall
|
||||
local i = msg:find("[C]: in function 'xpcall'", 1, true)
|
||||
if i then
|
||||
msg = msg:sub(1, i - 3)
|
||||
end
|
||||
|
||||
-- if the resulting stack trace is only one level deep, ignore it
|
||||
local n = select(2, msg:gsub('\n', '\n'))
|
||||
if n == 2 then
|
||||
msg = msg:sub(1, msg:find('\n', 1, true) - 1)
|
||||
end
|
||||
|
||||
return msg
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_io_open(fname, mode)
|
||||
m.value_openedfilename = fname
|
||||
m.value_openedfilemode = mode
|
||||
return {
|
||||
close = function()
|
||||
m.value_closedfile = true
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_io_output(f)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_os_writefile_ifnotequal(content, fname)
|
||||
m.value_openedfilename = fname
|
||||
m.value_closedfile = true
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_print(s)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.stub_utf8()
|
||||
end
|
@ -11,12 +11,6 @@
|
||||
local _ = {}
|
||||
|
||||
|
||||
--
|
||||
-- Define a namespace for the testing functions
|
||||
--
|
||||
|
||||
m.suppressed = {}
|
||||
|
||||
|
||||
--
|
||||
-- Capture stderr for testing.
|
||||
@ -299,177 +293,4 @@
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Test execution function
|
||||
--
|
||||
local _OS_host = _OS
|
||||
local _OPTIONS_host = _OPTIONS
|
||||
|
||||
local function error_handler(err)
|
||||
local msg = err
|
||||
|
||||
-- if the error doesn't include a stack trace, add one
|
||||
if not msg:find("stack traceback:", 1, true) then
|
||||
msg = debug.traceback(err, 2)
|
||||
end
|
||||
|
||||
-- trim of the trailing context of the originating xpcall
|
||||
local i = msg:find("[C]: in function 'xpcall'", 1, true)
|
||||
if i then
|
||||
msg = msg:sub(1, i - 3)
|
||||
end
|
||||
|
||||
-- if the resulting stack trace is only one level deep, ignore it
|
||||
local n = select(2, msg:gsub('\n', '\n'))
|
||||
if n == 2 then
|
||||
msg = msg:sub(1, msg:find('\n', 1, true) - 1)
|
||||
end
|
||||
|
||||
return msg
|
||||
end
|
||||
|
||||
|
||||
local function test_setup(suite, fn)
|
||||
_ACTION = "test"
|
||||
_OS = _OS_host
|
||||
|
||||
_OPTIONS = {}
|
||||
setmetatable(_OPTIONS, getmetatable(_OPTIONS_host))
|
||||
|
||||
stderr_capture = nil
|
||||
|
||||
premake.clearWarnings()
|
||||
premake.eol("\n")
|
||||
premake.escaper(nil)
|
||||
premake.indent("\t")
|
||||
premake.api.reset()
|
||||
|
||||
-- reset captured I/O values
|
||||
m.value_openedfilename = nil
|
||||
m.value_openedfilemode = nil
|
||||
m.value_closedfile = false
|
||||
|
||||
if suite.setup then
|
||||
return xpcall(suite.setup, error_handler)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function test_run(suite, fn)
|
||||
local result, err
|
||||
premake.capture(function()
|
||||
result, err = xpcall(fn, error_handler)
|
||||
end)
|
||||
return result, err
|
||||
end
|
||||
|
||||
|
||||
local function test_teardown(suite, fn)
|
||||
if suite.teardown then
|
||||
return xpcall(suite.teardown, error_handler)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function m.suppress(id)
|
||||
if type(id) == "table" then
|
||||
for i = 1, #id do
|
||||
m.suppress(id[i])
|
||||
end
|
||||
else
|
||||
m.suppressed[id] = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
m.print = print
|
||||
|
||||
|
||||
|
||||
function m.runTests(identifier)
|
||||
local test, err = m.getTestWithIdentifier(identifier)
|
||||
if not test then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local hooks = m.installTestingHooks()
|
||||
|
||||
test.passed = 0
|
||||
test.failed = 0
|
||||
|
||||
local startTime = os.clock()
|
||||
|
||||
if test.testFunction then
|
||||
_.runTest(test)
|
||||
elseif test.suite then
|
||||
_.runSuite(test)
|
||||
else
|
||||
_.runAll(test)
|
||||
end
|
||||
|
||||
local result = {
|
||||
passed = test.passed,
|
||||
failed = test.failed,
|
||||
start = startTime,
|
||||
elapsed = os.clock() - startTime
|
||||
}
|
||||
|
||||
m.removeTestingHooks(hooks)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.runAll(test)
|
||||
local suites = m.getSuites()
|
||||
for suiteName, suite in pairs(suites) do
|
||||
if not m.suppressed[suiteName] then
|
||||
test.suiteName = suiteName
|
||||
test.suite = suite
|
||||
_.runSuite(test)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.runSuite(test)
|
||||
for testName, testFunction in pairs(test.suite) do
|
||||
test.testName = testName
|
||||
test.testFunction = testFunction
|
||||
if m.isValid(test) and not m.suppressed[test.suiteName .. "." .. test.testName] then
|
||||
_.runTest(test)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function _.runTest(test)
|
||||
_TESTS_DIR = test.suite._TESTS_DIR
|
||||
_SCRIPT_DIR = test.suite._SCRIPT_DIR
|
||||
|
||||
local ok, err = test_setup(test.suite, test.testFunction)
|
||||
|
||||
if ok then
|
||||
ok, err = test_run(test.suite, test.testFunction)
|
||||
end
|
||||
|
||||
local tok, terr = test_teardown(test.suite, test.testFunction)
|
||||
ok = ok and tok
|
||||
err = err or terr
|
||||
|
||||
if not ok then
|
||||
m.print(string.format("%s.%s: %s", test.suiteName, test.testName, err))
|
||||
test.failed = test.failed + 1
|
||||
else
|
||||
test.passed = test.passed + 1
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user