2020-09-19 13:41:24 +02:00
|
|
|
local Token = require 'utils.token'
|
|
|
|
local Task = require 'utils.task'
|
2020-09-19 15:48:08 +02:00
|
|
|
local ModuleStore = require 'utils.test.module_store'
|
2020-09-20 20:13:07 +02:00
|
|
|
local Builder = require 'utils.test.builder'
|
|
|
|
local Event = require 'utils.event'
|
2020-09-19 13:41:24 +02:00
|
|
|
|
|
|
|
local pcall = pcall
|
|
|
|
|
|
|
|
local Public = {}
|
|
|
|
|
2020-11-13 22:06:01 +02:00
|
|
|
Public.events = {tests_run_finished = Event.generate_event_name('test_run_finished')}
|
2020-09-20 20:13:07 +02:00
|
|
|
|
2020-09-28 21:40:47 +02:00
|
|
|
local run_runnables_token
|
2020-09-25 21:33:46 +02:00
|
|
|
|
2020-09-29 21:50:24 +02:00
|
|
|
local function print_summary(data)
|
|
|
|
local pass_count = data.count - data.fail_count
|
|
|
|
data.player.print(table.concat {pass_count, ' of ', data.count, ' tests passed.'})
|
2020-09-20 20:13:07 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function mark_module_for_passed(module)
|
|
|
|
local any_fails = false
|
|
|
|
local all_ran = true
|
2020-09-19 13:41:24 +02:00
|
|
|
|
|
|
|
for _, child in pairs(module.children) do
|
2020-09-20 20:13:07 +02:00
|
|
|
local module_any_fails, module_all_ran = mark_module_for_passed(child)
|
|
|
|
any_fails = any_fails or module_any_fails
|
|
|
|
all_ran = all_ran and module_all_ran
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, test in pairs(module.tests) do
|
|
|
|
any_fails = any_fails or (test.passed == false)
|
|
|
|
all_ran = all_ran and (test.passed ~= nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
if any_fails then
|
|
|
|
module.passed = false
|
|
|
|
elseif all_ran then
|
|
|
|
module.passed = true
|
|
|
|
else
|
|
|
|
module.passed = nil
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
2020-09-20 20:13:07 +02:00
|
|
|
|
|
|
|
return any_fails, all_ran
|
|
|
|
end
|
|
|
|
|
|
|
|
local function mark_modules_for_passed()
|
|
|
|
mark_module_for_passed(ModuleStore.root_module)
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-09-20 20:13:07 +02:00
|
|
|
local function finish_test_run(data)
|
2020-09-29 21:50:24 +02:00
|
|
|
print_summary(data)
|
2020-09-20 20:13:07 +02:00
|
|
|
mark_modules_for_passed()
|
2020-09-28 21:40:47 +02:00
|
|
|
script.raise_event(Public.events.tests_run_finished, {player = data.player})
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-09-29 21:50:24 +02:00
|
|
|
local function print_error(player, test_name, error_message)
|
|
|
|
player.print(table.concat {"Failed - '", test_name, "': ", tostring(error_message)}, {r = 1})
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-09-29 21:50:24 +02:00
|
|
|
local function print_success(player, test_name)
|
|
|
|
player.print(table.concat {"Passed - '", test_name, "'"}, {g = 1})
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-09-25 21:33:46 +02:00
|
|
|
local function print_hook_error(hook)
|
2020-09-29 21:50:24 +02:00
|
|
|
hook.context.player.print(table.concat {'Failed ', hook.name, " hook -':", tostring(hook.error)}, {r = 1})
|
2020-09-25 21:33:46 +02:00
|
|
|
end
|
|
|
|
|
2020-11-13 22:06:01 +02:00
|
|
|
local function print_teardown_error(context, name, error_message)
|
|
|
|
context.player.print(table.concat {'Failed ', name, " teardown -':", error_message}, {r = 1})
|
|
|
|
end
|
|
|
|
|
2020-09-25 21:33:46 +02:00
|
|
|
local function record_hook_error_in_module(hook)
|
|
|
|
if hook.name == 'startup' then
|
|
|
|
hook.module.startup_error = hook.error
|
|
|
|
elseif hook.name == 'teardown' then
|
|
|
|
hook.module.teardown_error = hook.error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-29 21:50:24 +02:00
|
|
|
local function do_termination(data)
|
|
|
|
if not data.stop_on_first_error then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
data.player.print('Test run canceled due to stop on first error policy.')
|
|
|
|
finish_test_run(data)
|
|
|
|
data.index = -1
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2020-11-13 22:06:01 +02:00
|
|
|
local function run_teardown(teardown, errors)
|
|
|
|
local success, error_message = pcall(teardown)
|
|
|
|
|
|
|
|
if not success then
|
|
|
|
errors[#errors + 1] = error_message
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function do_teardowns(context, name)
|
|
|
|
local teardowns = context._teardowns
|
|
|
|
local errors = {}
|
|
|
|
|
|
|
|
for i = 1, #teardowns do
|
|
|
|
run_teardown(teardowns[i], errors)
|
|
|
|
end
|
|
|
|
|
|
|
|
if #errors > 0 then
|
|
|
|
local error_message = table.concat(errors, '\n')
|
|
|
|
print_teardown_error(context, name, error_message)
|
|
|
|
|
|
|
|
return error_message
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2020-09-25 21:33:46 +02:00
|
|
|
local function run_hook(hook)
|
2020-09-28 21:40:47 +02:00
|
|
|
local context = hook.context
|
|
|
|
local steps = context._steps
|
2020-09-25 21:33:46 +02:00
|
|
|
local current_step = hook.current_step
|
2020-09-29 21:50:24 +02:00
|
|
|
|
2020-09-25 21:33:46 +02:00
|
|
|
local func
|
|
|
|
if current_step == 0 then
|
|
|
|
func = hook.func
|
|
|
|
else
|
|
|
|
func = steps[current_step].func
|
|
|
|
end
|
2020-09-29 21:50:24 +02:00
|
|
|
|
2020-09-28 21:40:47 +02:00
|
|
|
local success, return_value = pcall(func, context)
|
2020-09-25 21:33:46 +02:00
|
|
|
|
|
|
|
if not success then
|
|
|
|
hook.error = return_value
|
|
|
|
print_hook_error(hook)
|
|
|
|
record_hook_error_in_module(hook)
|
2020-11-13 22:06:01 +02:00
|
|
|
do_teardowns(context, hook.name)
|
2020-09-25 21:33:46 +02:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2020-11-13 22:06:01 +02:00
|
|
|
if current_step ~= #steps then
|
|
|
|
return nil
|
2020-09-25 21:33:46 +02:00
|
|
|
end
|
|
|
|
|
2020-11-13 22:06:01 +02:00
|
|
|
local error_message = do_teardowns(context, hook.name)
|
|
|
|
if error_message then
|
|
|
|
hook.error = error_message
|
|
|
|
record_hook_error_in_module(hook)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
2020-09-25 21:33:46 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function do_hook(hook, data)
|
|
|
|
local hook_success = run_hook(hook)
|
2020-09-29 21:50:24 +02:00
|
|
|
|
|
|
|
if hook_success == false and do_termination(data) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-09-25 21:33:46 +02:00
|
|
|
if hook_success == nil then
|
2020-09-26 21:02:53 +02:00
|
|
|
local step_index = hook.current_step + 1
|
2020-09-28 21:40:47 +02:00
|
|
|
local step = hook.context._steps[step_index]
|
2020-09-26 21:02:53 +02:00
|
|
|
hook.current_step = step_index
|
2020-09-28 21:40:47 +02:00
|
|
|
Task.set_timeout_in_ticks(step.delay or 1, run_runnables_token, data)
|
2020-09-26 21:02:53 +02:00
|
|
|
return
|
2020-09-25 21:33:46 +02:00
|
|
|
end
|
|
|
|
|
2020-09-26 21:02:53 +02:00
|
|
|
data.index = data.index + 1
|
2020-09-28 21:40:47 +02:00
|
|
|
Task.set_timeout_in_ticks(1, run_runnables_token, data)
|
2020-09-25 21:33:46 +02:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-09-19 13:41:24 +02:00
|
|
|
local function run_test(test)
|
2020-09-28 21:40:47 +02:00
|
|
|
local context = test.context
|
|
|
|
local steps = context._steps
|
2020-09-19 16:56:50 +02:00
|
|
|
local current_step = test.current_step
|
2020-09-29 21:50:24 +02:00
|
|
|
|
2020-09-19 16:56:50 +02:00
|
|
|
local func
|
|
|
|
if current_step == 0 then
|
|
|
|
func = test.func
|
|
|
|
else
|
|
|
|
func = steps[current_step].func
|
|
|
|
end
|
2020-09-29 21:50:24 +02:00
|
|
|
|
2020-09-28 21:40:47 +02:00
|
|
|
local success, return_value = pcall(func, context)
|
2020-09-19 13:41:24 +02:00
|
|
|
|
|
|
|
if not success then
|
2020-09-29 21:50:24 +02:00
|
|
|
print_error(context.player, test.name, return_value)
|
2020-09-20 20:13:07 +02:00
|
|
|
test.passed = false
|
|
|
|
test.error = return_value
|
2020-11-13 22:06:01 +02:00
|
|
|
do_teardowns(context, test.name)
|
2020-09-19 13:41:24 +02:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2020-11-13 22:06:01 +02:00
|
|
|
if current_step ~= #steps then
|
|
|
|
return nil
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-11-13 22:06:01 +02:00
|
|
|
local error_message = do_teardowns(context, test.name)
|
|
|
|
if error_message then
|
|
|
|
test.passed = false
|
|
|
|
test.error = error_message
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
print_success(context.player, test.name)
|
|
|
|
test.passed = true
|
|
|
|
return true
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-09-25 21:33:46 +02:00
|
|
|
local function do_test(test, data)
|
2020-09-19 13:41:24 +02:00
|
|
|
local success = run_test(test)
|
|
|
|
|
|
|
|
if success == false then
|
2020-09-19 15:48:08 +02:00
|
|
|
data.count = data.count + 1
|
2020-09-19 13:41:24 +02:00
|
|
|
data.fail_count = data.fail_count + 1
|
2020-09-29 21:50:24 +02:00
|
|
|
|
|
|
|
if do_termination(data) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-09-25 21:33:46 +02:00
|
|
|
data.index = data.index + 1
|
2020-09-28 21:40:47 +02:00
|
|
|
Task.set_timeout_in_ticks(1, run_runnables_token, data)
|
2020-09-19 13:41:24 +02:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if success == true then
|
2020-09-19 15:48:08 +02:00
|
|
|
data.count = data.count + 1
|
2020-09-25 21:33:46 +02:00
|
|
|
data.index = data.index + 1
|
2020-09-28 21:40:47 +02:00
|
|
|
Task.set_timeout_in_ticks(1, run_runnables_token, data)
|
2020-09-19 13:41:24 +02:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-09-19 16:56:50 +02:00
|
|
|
local step_index = test.current_step + 1
|
|
|
|
test.current_step = step_index
|
2020-09-28 21:40:47 +02:00
|
|
|
local step = test.context._steps[step_index]
|
|
|
|
Task.set_timeout_in_ticks(step.delay or 1, run_runnables_token, data)
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-09-28 21:40:47 +02:00
|
|
|
local function run_runnables(data)
|
2020-09-25 21:33:46 +02:00
|
|
|
local index = data.index
|
|
|
|
local runnable = data.runnables[index]
|
|
|
|
|
|
|
|
if runnable == nil then
|
|
|
|
finish_test_run(data)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if runnable.is_hook then
|
|
|
|
do_hook(runnable, data)
|
2020-09-29 21:50:24 +02:00
|
|
|
else
|
|
|
|
do_test(runnable, data)
|
2020-09-25 21:33:46 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-28 21:40:47 +02:00
|
|
|
run_runnables_token = Token.register(run_runnables)
|
2020-09-19 13:41:24 +02:00
|
|
|
|
2020-09-29 21:50:24 +02:00
|
|
|
local function validate_options(options)
|
|
|
|
options = options or {}
|
|
|
|
options.stop_on_first_error = options.stop_on_first_error or false
|
|
|
|
return options
|
|
|
|
end
|
|
|
|
|
|
|
|
local function run(runnables, player, options)
|
|
|
|
options = validate_options(options)
|
2020-11-13 22:06:01 +02:00
|
|
|
run_runnables({
|
|
|
|
runnables = runnables,
|
|
|
|
player = player,
|
|
|
|
index = 1,
|
|
|
|
count = 0,
|
|
|
|
fail_count = 0,
|
|
|
|
stop_on_first_error = options.stop_on_first_error
|
|
|
|
})
|
2020-09-19 13:41:24 +02:00
|
|
|
end
|
|
|
|
|
2020-09-29 21:50:24 +02:00
|
|
|
function Public.run_module(module, player, options)
|
2020-09-28 21:40:47 +02:00
|
|
|
local runnables = Builder.build_module_for_run(module or ModuleStore.root_module, player)
|
2020-09-29 21:50:24 +02:00
|
|
|
run(runnables, player, options)
|
2020-09-20 20:13:07 +02:00
|
|
|
end
|
|
|
|
|
2020-09-29 21:50:24 +02:00
|
|
|
function Public.run_test(test, player, options)
|
2020-09-28 21:40:47 +02:00
|
|
|
local runnables = Builder.build_test_for_run(test, player)
|
2020-09-29 21:50:24 +02:00
|
|
|
run(runnables, player, options)
|
2020-09-20 20:13:07 +02:00
|
|
|
end
|
2020-09-19 13:41:24 +02:00
|
|
|
|
|
|
|
return Public
|