From df47b5aa02f929ec7bbd9a4464a2a7a571556ce3 Mon Sep 17 00:00:00 2001 From: James Gillham Date: Fri, 25 Sep 2020 20:33:46 +0100 Subject: [PATCH] module startup/teardown hooks --- features/landfill_remover_test.lua | 14 +++++ utils/test/builder.lua | 79 ++++++++++++++++++++++--- utils/test/declare.lua | 2 + utils/test/module_store.lua | 42 +++++++++++++ utils/test/runner.lua | 95 ++++++++++++++++++++++++------ utils/test/viewer.lua | 28 ++++++++- 6 files changed, 232 insertions(+), 28 deletions(-) diff --git a/features/landfill_remover_test.lua b/features/landfill_remover_test.lua index 54cc08af..0fa95d6a 100644 --- a/features/landfill_remover_test.lua +++ b/features/landfill_remover_test.lua @@ -8,6 +8,20 @@ local config = global.config.landfill_remover Declare.module( 'landfill remover', function() + Declare.module_startup( + function() + game.print('landfill remover startup') + error('landfill remover startup') + end + ) + + Declare.module_teardown( + function() + game.print('landfill remover teardown') + error('landfill remover teardown') + end + ) + Declare.test( 'can remove landfill', function() diff --git a/utils/test/builder.lua b/utils/test/builder.lua index c342f38d..90285b55 100644 --- a/utils/test/builder.lua +++ b/utils/test/builder.lua @@ -15,6 +15,7 @@ local function init_inner(module, depth) count = count + 1 tests[#tests + 1] = { name = name, + module = module, func = func, steps = nil, current_step = nil, @@ -46,38 +47,100 @@ function Public.get_root_modules() return ModuleStore.root_module end +local function prepare_pre_module_hooks(module, runnables) + local startup_func = module.startup_func + if startup_func then + runnables[#runnables + 1] = { + is_hook = true, + name = 'startup', + module = module, + func = startup_func, + steps = Steps.new(), + current_step = 0, + error = nil + } + end +end + +local function build_pre_module_hooks(module, runnables) + if module == nil then + return + end + + build_pre_module_hooks(module.parent, runnables) + prepare_pre_module_hooks(module, runnables) +end + +local function prepare_post_module_hooks(module, runnables) + local teardown_func = module.teardown_func + if teardown_func then + runnables[#runnables + 1] = { + is_hook = true, + name = 'teardown', + module = module, + func = teardown_func, + steps = Steps.new(), + current_step = 0, + error = nil + } + end +end + +local function build_post_module_hooks(module, runnables) + if module == nil then + return + end + + prepare_post_module_hooks(module, runnables) + build_post_module_hooks(module.parent, runnables) +end + local function prepare_test(test) test.steps = Steps.new() test.current_step = 0 test.passed = nil test.error = nil + return test end -local function prepare_module(module, tests) +local function prepare_module(module, runnables) module.passed = nil + prepare_pre_module_hooks(module, runnables) for _, test in pairs(module.tests) do prepare_test(test) - tests[#tests + 1] = test + runnables[#runnables + 1] = test end for _, child in pairs(module.children) do - prepare_module(child, tests) + prepare_module(child, runnables) end + + prepare_post_module_hooks(module, runnables) end function Public.build_test_for_run(test) Public.init() - prepare_test(test) - return test + + local runnables = {} + + build_pre_module_hooks(test.module, runnables) + runnables[#runnables + 1] = prepare_test(test) + build_post_module_hooks(test.module, runnables) + + return runnables end function Public.build_module_for_run(module) Public.init() - local tests = {} - prepare_module(module, tests) - return tests + local runnables = {} + + build_pre_module_hooks(module.parent, runnables) + prepare_module(module, runnables) + build_post_module_hooks(module.parent, runnables) + + return runnables end return Public diff --git a/utils/test/declare.lua b/utils/test/declare.lua index 84870765..d5dc2321 100644 --- a/utils/test/declare.lua +++ b/utils/test/declare.lua @@ -4,5 +4,7 @@ local Public = {} Public.module = ModuleStore.module Public.test = ModuleStore.test +Public.module_startup = ModuleStore.module_startup +Public.module_teardown = ModuleStore.module_teardown return Public diff --git a/utils/test/module_store.lua b/utils/test/module_store.lua index 383dd1b3..315f633a 100644 --- a/utils/test/module_store.lua +++ b/utils/test/module_store.lua @@ -3,7 +3,16 @@ local Public = {} local function new_module(module_name) return { name = module_name, + parent = nil, children = {}, + startup_func = nil, + startup_steps = nil, + startup_current_step = nil, + startup_error = nil, + teardown_func = nil, + teardown_steps = nil, + teardown_current_step = nil, + teardown_error = nil, test_funcs = {}, tests = nil, is_open = true, @@ -25,6 +34,7 @@ local function add_module(module_name, module_func, parent) if not module then module = new_module(module_name) parent_children[module_name] = module + module.parent = parent_module end parent_module = module @@ -75,4 +85,36 @@ function Public.test(test_name, test_func) test_funcs[test_name] = test_func end +function Public.module_startup(startup_func) + if type(startup_func) ~= 'function' then + error('startup_func must be of type function.') + end + + if parent_module == nil then + error('root module can not have startup_func.') + end + + if parent_module.startup_func ~= nil then + error('startup_func can not be declared twice for the same module.') + end + + parent_module.startup_func = startup_func +end + +function Public.module_teardown(teardown_func) + if type(teardown_func) ~= 'function' then + error('teardown_func must be of type function.') + end + + if parent_module == nil then + error('root module can not have teardown_func.') + end + + if parent_module.teardown_func ~= nil then + error('teardown_func can not be declared twice for the same module.') + end + + parent_module.teardown_func = teardown_func +end + return Public diff --git a/utils/test/runner.lua b/utils/test/runner.lua index d6cad476..300e11c7 100644 --- a/utils/test/runner.lua +++ b/utils/test/runner.lua @@ -12,6 +12,8 @@ Public.events = { tests_run_finished = Event.generate_event_name('test_run_finished') } +local run_tests_token + local function print_summary(count, fail_count) local pass_count = count - fail_count game.print(table.concat {pass_count, ' of ', count, ' tests passed.'}) @@ -61,6 +63,55 @@ local function print_success(test_name) game.print(table.concat {"Passed - '", test_name, "'"}, {g = 1}) end +local function print_hook_error(hook) + game.print(table.concat {'Failed ', hook.name, " hook -':", tostring(hook.error)}, {r = 1}) +end + +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 + +local function run_hook(hook) + local steps = hook.steps + local current_step = hook.current_step + local func + if current_step == 0 then + func = hook.func + else + func = steps[current_step].func + end + local success, return_value = pcall(func, steps) + + if not success then + hook.error = return_value + print_hook_error(hook) + record_hook_error_in_module(hook) + return false + end + + if current_step == #steps then + return true + end + + return nil +end + +local function do_hook(hook, data) + local hook_success = run_hook(hook) + if hook_success == nil then + hook.current_step = hook.current_step + 1 + else + data.index = data.index + 1 + end + + Task.set_timeout_in_ticks(1, run_tests_token, data) + return +end + local function run_test(test) local steps = test.steps local current_step = test.current_step @@ -88,29 +139,20 @@ local function run_test(test) return nil end -local run_tests_token -local function run_tests(data) - local index = data.index - - local test = data.tests[index] - if test == nil then - finish_test_run(data) - return - end - +local function do_test(test, data) local success = run_test(test) if success == false then data.count = data.count + 1 data.fail_count = data.fail_count + 1 - data.index = index + 1 + data.index = data.index + 1 Task.set_timeout_in_ticks(1, run_tests_token, data) return end if success == true then data.count = data.count + 1 - data.index = index + 1 + data.index = data.index + 1 Task.set_timeout_in_ticks(1, run_tests_token, data) return end @@ -121,20 +163,37 @@ local function run_tests(data) Task.set_timeout_in_ticks(step.delay or 1, run_tests_token, data) end +local function run_tests(data) + 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) + return + end + + do_test(runnable, data) +end + run_tests_token = Token.register(run_tests) -local function run(tests) - run_tests({tests = tests, index = 1, count = 0, fail_count = 0}) +local function run(runnables) + run_tests({runnables = runnables, index = 1, count = 0, fail_count = 0}) end function Public.run_module(module) - local tests = Builder.build_module_for_run(module or ModuleStore.root_module) - run(tests) + local runnables = Builder.build_module_for_run(module or ModuleStore.root_module) + run(runnables) end function Public.run_test(test) - Builder.build_test_for_run(test) - run({test}) + local runnables = Builder.build_test_for_run(test) + run(runnables) end return Public diff --git a/utils/test/viewer.lua b/utils/test/viewer.lua index a975113c..d521e4be 100644 --- a/utils/test/viewer.lua +++ b/utils/test/viewer.lua @@ -23,6 +23,15 @@ local error_test_box_name = Gui.uid_name() local selected_modules = {} local selected_tests = {} +local function get_module_state(module) + local passed = module.passed + if passed == false or module.startup_error or module.teardown_error then + return false + end + + return passed +end + local function set_selected_style(style, selected) if selected then style.font_color = Color.orange @@ -77,7 +86,7 @@ local function draw_tests_module(container, module) local is_selected = selected_modules[module] set_selected_style(label_style, is_selected) if not is_selected then - set_passed_style(label_style, module.passed) + set_passed_style(label_style, get_module_state(module)) end Gui.set_data(label, {module = module, container = container}) @@ -221,7 +230,22 @@ Gui.on_click( end local error_text_box = get_error_text_box(event.player) - error_text_box.text = '' + if is_selected then + local errors = {} + if module.startup_error then + errors[#errors + 1] = 'startup error: ' + errors[#errors + 1] = module.startup_error + errors[#errors + 1] = '\n\n' + end + if module.teardown_error then + errors[#errors + 1] = 'teardown error: ' + errors[#errors + 1] = module.teardown_error + end + + error_text_box.text = table.concat(errors) + else + error_text_box.text = '' + end redraw_tests(container) end