2016-05-25 20:55:49 +00:00
---
-- 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 _ = { }
2018-06-14 14:51:36 +00:00
function m . runTest ( tests )
2016-05-25 20:55:49 +00:00
local failed = 0
2018-04-30 13:41:45 +00:00
local failedTests = { }
2016-05-25 20:55:49 +00:00
local suites = m.getSuites ( )
2018-06-14 14:51:36 +00:00
local suitesKeys , suiteTestsKeys , totalTestCount = _.preprocessTests ( suites , tests )
2016-05-25 20:55:49 +00:00
2018-04-30 13:41:45 +00:00
_.log ( term.lightGreen , " [==========] " , string.format ( " Running %d tests from %d test suites. " , totalTestCount , # suitesKeys ) )
local startTime = os.clock ( )
for index , suiteName in ipairs ( suitesKeys ) do
suite = suites [ suiteName ]
if not m.isSuppressed ( suiteName ) then
local test = {
suiteName = suiteName ,
suite = suite
}
2016-05-25 20:55:49 +00:00
2018-04-30 13:41:45 +00:00
local suiteFailed , suiteFailedTests = _.runTestSuite ( test , suiteTestsKeys [ suiteName ] )
2016-05-25 20:55:49 +00:00
failed = failed + suiteFailed
2018-04-30 13:41:45 +00:00
failedTests = table.join ( failedTests , suiteFailedTests )
2016-05-25 20:55:49 +00:00
end
end
2018-04-30 13:41:45 +00:00
_.log ( term.lightGreen , " [==========] " , string.format ( " %d tests from %d test suites ran. (%.0f ms total) " , totalTestCount , # suitesKeys , ( os.clock ( ) - startTime ) * 1000 ) )
_.log ( term.lightGreen , " [ PASSED ] " , string.format ( " %d tests. " , totalTestCount - failed ) )
if failed > 0 then
_.log ( term.lightRed , " [ FAILED ] " , string.format ( " %d tests, listed below: " , failed ) )
for index , testName in ipairs ( failedTests ) do
_.log ( term.lightRed , " [ FAILED ] " , " " .. testName )
end
end
return ( totalTestCount - failed ) , failed
2016-05-25 20:55:49 +00:00
end
2018-04-30 13:41:45 +00:00
function _ . runTestSuite ( test , keys )
2016-05-25 20:55:49 +00:00
local failed = 0
2018-04-30 13:41:45 +00:00
local failedTests = { }
_.log ( term.lightGreen , " [----------] " , string.format ( " %d tests from %s " , # keys , test.suiteName ) )
local startTime = os.clock ( )
2016-05-25 20:55:49 +00:00
2017-09-15 00:22:44 +00:00
if test.suite ~= nil then
2018-04-30 13:41:45 +00:00
for index , testName in ipairs ( keys ) do
testFunction = test.suite [ testName ]
2017-09-15 00:22:44 +00:00
test.testName = testName
test.testFunction = testFunction
if m.isValid ( test ) and not m.isSuppressed ( test.suiteName .. " . " .. test.testName ) then
2018-04-30 13:41:45 +00:00
local err = _.runTest ( test )
if err then
failed = failed + 1
table.insert ( failedTests , test.suiteName .. " . " .. test.testName .. " \n " .. err )
end
2017-09-15 00:22:44 +00:00
end
2016-05-25 20:55:49 +00:00
end
end
2018-04-30 13:41:45 +00:00
_.log ( term.lightGreen , " [----------] " , string.format ( " %d tests from %s (%.0f ms total) \n " , # keys , test.suiteName , ( os.clock ( ) - startTime ) * 1000 ) )
return failed , failedTests
2016-05-25 20:55:49 +00:00
end
function _ . runTest ( test )
2018-04-30 13:41:45 +00:00
_.log ( term.lightGreen , " [ RUN ] " , string.format ( " %s.%s " , test.suiteName , test.testName ) )
local startTime = os.clock ( )
2017-07-04 20:34:49 +00:00
local cwd = os.getcwd ( )
2016-05-25 20:55:49 +00:00
local hooks = _.installTestingHooks ( )
_TESTS_DIR = test.suite . _TESTS_DIR
_SCRIPT_DIR = test.suite . _SCRIPT_DIR
2017-08-01 16:46:12 +00:00
m.suiteName = test.suiteName
m.testName = test.testName
2016-05-25 20:55:49 +00:00
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
_.removeTestingHooks ( hooks )
2017-07-04 20:34:49 +00:00
os.chdir ( cwd )
2016-05-25 20:55:49 +00:00
2016-06-03 21:00:06 +00:00
if ok then
2018-04-30 13:41:45 +00:00
_.log ( term.lightGreen , " [ OK ] " , string.format ( " %s.%s (%.0f ms) " , test.suiteName , test.testName , ( os.clock ( ) - startTime ) * 1000 ) )
return nil
2016-06-03 21:00:06 +00:00
else
2018-04-30 13:41:45 +00:00
_.log ( term.lightRed , " [ FAILED ] " , string.format ( " %s.%s (%.0f ms) " , test.suiteName , test.testName , ( os.clock ( ) - startTime ) * 1000 ) )
m.print ( string.format ( " %s " , err ) )
return err
2016-06-03 21:00:06 +00:00
end
2016-05-25 20:55:49 +00:00
end
2018-04-30 13:41:45 +00:00
function _ . log ( color , left , right )
term.pushColor ( color )
io.write ( left )
term.popColor ( )
m.print ( right )
end
2018-06-14 14:51:36 +00:00
function _ . preprocessTests ( suites , filters )
2018-04-30 13:41:45 +00:00
local suitesKeys = { }
local suiteTestsKeys = { }
local totalTestCount = 0
2018-06-14 14:51:36 +00:00
for i , filter in ipairs ( filters ) do
for suiteName , suite in pairs ( suites ) do
if not m.isSuppressed ( suiteName ) and suite ~= nil and ( not filter.suiteName or filter.suiteName == suiteName ) then
local test = { }
test.suiteName = suiteName
test.suite = suite
if not table.contains ( suitesKeys , suiteName ) then
table.insertsorted ( suitesKeys , suiteName )
suiteTestsKeys [ suiteName ] = { }
end
2018-04-30 13:41:45 +00:00
2018-06-14 14:51:36 +00:00
for testName , testFunction in pairs ( suite ) do
test.testName = testName
test.testFunction = testFunction
2018-04-30 13:41:45 +00:00
2018-06-14 14:51:36 +00:00
if m.isValid ( test ) and not m.isSuppressed ( test.suiteName .. " . " .. test.testName ) and ( not filter.testName or filter.testName == testName ) then
if not table.contains ( suiteTestsKeys [ suiteName ] , testName ) then
table.insertsorted ( suiteTestsKeys [ suiteName ] , testName )
totalTestCount = totalTestCount + 1
end
end
2018-04-30 13:41:45 +00:00
end
end
end
end
return suitesKeys , suiteTestsKeys , totalTestCount
end
2016-05-25 20:55:49 +00:00
function _ . installTestingHooks ( )
local hooks = { }
hooks.action = _ACTION
hooks.options = _OPTIONS
2017-04-11 20:39:25 +00:00
hooks.targetOs = _TARGET_OS
2016-05-25 20:55:49 +00:00
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
2017-06-21 16:52:25 +00:00
hooks.setTextColor = term.setTextColor
2016-05-25 20:55:49 +00:00
2016-05-26 23:57:27 +00:00
local mt = getmetatable ( io.stderr )
_.builtin_write = mt.write
mt.write = _.stub_stderr_write
2017-06-13 20:37:21 +00:00
_OPTIONS = table.shallowcopy ( _OPTIONS ) or { }
2016-05-25 20:55:49 +00:00
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
2017-06-21 16:52:25 +00:00
term.setTextColor = _.stub_setTextColor
2016-05-25 20:55:49 +00:00
stderr_capture = nil
p.clearWarnings ( )
p.eol ( " \n " )
p.escaper ( nil )
p.indent ( " \t " )
p.api . reset ( )
2016-05-26 23:57:27 +00:00
m.stderr_capture = nil
2016-05-25 20:55:49 +00:00
m.value_openedfilename = nil
m.value_openedfilemode = nil
m.value_closedfile = false
return hooks
end
2016-05-26 23:57:27 +00:00
2016-05-25 20:55:49 +00:00
function _ . removeTestingHooks ( hooks )
2017-08-01 16:46:12 +00:00
p.action . set ( hooks.action )
2016-05-25 20:55:49 +00:00
_OPTIONS = hooks.options
2017-04-11 20:39:25 +00:00
_TARGET_OS = hooks.targetOs
2016-05-25 20:55:49 +00:00
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
2017-06-21 16:52:25 +00:00
term.setTextColor = hooks.setTextColor
2016-05-26 23:57:27 +00:00
local mt = getmetatable ( io.stderr )
mt.write = _.builtin_write
2016-05-25 20:55:49 +00:00
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 {
2017-08-02 09:57:15 +00:00
read = function ( )
end ,
2016-05-25 20:55:49 +00:00
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
2016-05-26 23:57:27 +00:00
function _ . stub_stderr_write ( ... )
if select ( 1 , ... ) == io.stderr then
m.stderr_capture = ( m.stderr_capture or " " ) .. select ( 2 , ... )
else
return _.builtin_write ( ... )
end
end
2016-05-25 20:55:49 +00:00
function _ . stub_utf8 ( )
end
2017-06-21 16:52:25 +00:00
function _ . stub_setTextColor ( )
end