From eb7b43cf1ce580e3ccd9c272dfd042d5bb25716c Mon Sep 17 00:00:00 2001 From: James Gillham Date: Fri, 13 Nov 2020 20:06:01 +0000 Subject: [PATCH] Add player settings for corpse pings + tests. --- features/corpse_util.lua | 47 ++++++- features/corpse_util_tests.lua | 235 +++++++++++++++++++++++++++++++++ locale/en/redmew_features.cfg | 5 +- utils/test/assert.lua | 23 ++++ utils/test/context.lua | 13 +- utils/test/event_factory.lua | 20 +-- utils/test/helper.lua | 133 ++++++++----------- utils/test/runner.lua | 84 +++++++++--- 8 files changed, 441 insertions(+), 119 deletions(-) create mode 100644 features/corpse_util_tests.lua diff --git a/features/corpse_util.lua b/features/corpse_util.lua index ee85e5bd..707dc535 100644 --- a/features/corpse_util.lua +++ b/features/corpse_util.lua @@ -3,6 +3,18 @@ local Global = require 'utils.global' local Task = require 'utils.task' local Token = require 'utils.token' local Utils = require 'utils.core' +local Settings = require 'utils.redmew_settings' + +local Public = {} + +local ping_own_death_name = 'corpse_util.ping_own_death' +local ping_other_death_name = 'corpse_util.ping_other_death' + +Public.ping_own_death_name = ping_own_death_name +Public.ping_other_death_name = ping_other_death_name + +Settings.register(ping_own_death_name, Settings.types.boolean, true, 'corpse_util.ping_own_death') +Settings.register(ping_other_death_name, Settings.types.boolean, false, 'corpse_util.ping_other_death') local player_corpses = {} @@ -49,13 +61,26 @@ local function player_died(event) return end - player.force.print({ - 'corpse_util.marked_tag', - player.name, - string.format('%.1f', position.x), - string.format('%.1f', position.y), - player.surface.name - }) + if Settings.get(player_index, ping_own_death_name) then + player.print({ + 'corpse_util.own_corpse_location', + string.format('%.1f', position.x), + string.format('%.1f', position.y), + player.surface.name + }) + end + + for _, other_player in pairs(player.force.players) do + if other_player ~= player and Settings.get(other_player.index, ping_other_death_name) then + other_player.print({ + 'corpse_util.other_corpse_location', + player.name, + string.format('%.1f', position.x), + string.format('%.1f', position.y), + player.surface.name + }) + end + end player_corpses[player_index * 0x100000000 + tick] = tag end @@ -144,3 +169,11 @@ Event.add(defines.events.on_player_died, player_died) Event.add(defines.events.on_character_corpse_expired, corpse_expired) Event.add(defines.events.on_pre_player_mined_item, mined_entity) Event.add(defines.events.on_gui_opened, on_gui_opened) + +function Public.clear() + table.clear_table(player_corpses) +end + +Public._player_died = player_died + +return Public diff --git a/features/corpse_util_tests.lua b/features/corpse_util_tests.lua new file mode 100644 index 00000000..e5f63213 --- /dev/null +++ b/features/corpse_util_tests.lua @@ -0,0 +1,235 @@ +local Declare = require 'utils.test.declare' +local EventFactory = require 'utils.test.event_factory' +local Assert = require 'utils.test.assert' +local Helper = require 'utils.test.helper' +local Settings = require 'utils.redmew_settings' +local CorpseUtil = require 'features.corpse_util' + +local function test_teardown(context) + context:add_teardown(CorpseUtil.clear) +end + +local function declare_test(name, func) + local function test_func(context) + test_teardown(context) + func(context) + end + + Declare.test(name, test_func) +end + +Declare.module({'features', 'corpse_util'}, function() + local teardown + + Declare.module_startup(function(context) + teardown = Helper.startup_test_surface(context) + + -- wait for surface to be charted, needed before a tag can be created. + context:next(function() + local player = context.player + Helper.wait_for_chunk_to_be_charted(context, player.force, player.surface, {0, 0}) + end) + end) + + Declare.module_teardown(function() + teardown() + end) + + local function change_settings_for_test(context, key, value) + local player_index = context.player.index + + local current_value = Settings.get(player_index, key) + Settings.set(player_index, key, value) + + context:add_teardown(function() + Settings.set(player_index, key, current_value) + end) + end + + local function fake_death(player) + local surface = player.surface + local position = player.position + + local entity = surface.create_entity { + name = 'character-corpse', + position = position, + player_index = player.index + } + + if not entity or not entity.valid then + error('no corpse') + end + + return EventFactory.on_player_died(player.index) + end + + declare_test('ping player corpse location when died', function(context) + -- Arrange. + local player = context.player + + local actual_text + + Helper.modify_lua_object(context, player, 'print', function(text) + actual_text = text + end) + + Helper.modify_lua_object(context, game, 'get_player', function() + return player + end) + + local event = fake_death(player) + + -- Act. + CorpseUtil._player_died(event) + + -- Assert. + local expected = {'corpse_util.own_corpse_location', '0.0', '0.0', player.surface.name} + Assert.table_equal(expected, actual_text) + end) + + declare_test('ping other player corpse location when other player died', function(context) + -- Arrange. + local player = context.player + local force = player.force + + local actual_text + + local second_player = { + index = 2, + valid = true, + name = 'second_player', + surface = player.surface, + force = force, + print = function() + end, + position = EventFactory.position({1, 1}) + } + + change_settings_for_test(context, CorpseUtil.ping_other_death_name, true) + + Helper.modify_lua_object(context, game, 'get_player', function(index) + if index == player.index then + return player + end + + if index == second_player.index then + return second_player + end + end) + + Helper.modify_lua_object(context, force, 'players', {player, second_player}) + + Helper.modify_lua_object(context, player, 'print', function(text) + actual_text = text + end) + + local event = fake_death(second_player) + + -- Act. + CorpseUtil._player_died(event) + + -- Assert. + local expected = {'corpse_util.other_corpse_location', second_player.name, '1.0', '1.0', player.surface.name} + Assert.table_equal(expected, actual_text) + end) + + declare_test('do not ping player corpse location when died and setting disabled', function(context) + -- Arrange. + local player = context.player + change_settings_for_test(context, CorpseUtil.ping_own_death_name, false) + + local actual_text + + Helper.modify_lua_object(context, player, 'print', function(text) + actual_text = text + end) + + Helper.modify_lua_object(context, game, 'get_player', function() + return player + end) + + local event = fake_death(player) + + -- Act. + CorpseUtil._player_died(event) + + -- Assert. + Assert.is_nil(actual_text) + end) + + declare_test('do not ping other player corpse location when other player died and settings disabled', + function(context) + -- Arrange. + local player = context.player + local force = player.force + + local actual_text + + local second_player = { + index = 2, + valid = true, + name = 'second_player', + surface = player.surface, + force = force, + print = function() + end, + position = EventFactory.position({1, 1}) + } + + change_settings_for_test(context, CorpseUtil.ping_other_death_name, false) + + Helper.modify_lua_object(context, game, 'get_player', function(index) + if index == player.index then + return player + end + + if index == second_player.index then + return second_player + end + end) + + Helper.modify_lua_object(context, force, 'players', {player, second_player}) + + Helper.modify_lua_object(context, player, 'print', function(text) + actual_text = text + end) + + local event = fake_death(second_player) + + -- Act. + CorpseUtil._player_died(event) + + -- Assert. + Assert.is_nil(actual_text) + end) + + declare_test('do not ping other player corpse location for self', function(context) + -- Arrange. + local player = context.player + local force = player.force + + local actual_text + + change_settings_for_test(context, CorpseUtil.ping_own_death_name, false) + change_settings_for_test(context, CorpseUtil.ping_other_death_name, true) + + Helper.modify_lua_object(context, game, 'get_player', function() + return player + end) + + Helper.modify_lua_object(context, player, 'force', force) + Helper.modify_lua_object(context, force, 'players', {player}) + + Helper.modify_lua_object(context, player, 'print', function(text) + actual_text = text + end) + + local event = fake_death(player) + + -- Act. + CorpseUtil._player_died(event) + + -- Assert. + Assert.is_nil(actual_text) + end) +end) diff --git a/locale/en/redmew_features.cfg b/locale/en/redmew_features.cfg index bab529aa..8b1e837e 100644 --- a/locale/en/redmew_features.cfg +++ b/locale/en/redmew_features.cfg @@ -179,4 +179,7 @@ instructions=Select a brush tile to replace [item=refined-concrete] and [item=re no_place_landfill=Coloured concrete can not be placed on landfill tiles. [corpse_util] -marked_tag=__1__'s corpse has been marked on the map at [gps=__2__,__3__,__4__] +ping_own_death=Ping the location when you die. +ping_other_death=Ping the location when other players die. +own_corpse_location=[color=red][Corpse][/color] Your corpse is located at [gps=__1__,__2__,__3__] +other_corpse_location=[Color=red][Corpse][/Color] __1__'s corpse is located at [gps=__2__,__3__,__4__] diff --git a/utils/test/assert.lua b/utils/test/assert.lua index a806e6b8..122a7cb6 100644 --- a/utils/test/assert.lua +++ b/utils/test/assert.lua @@ -1,3 +1,4 @@ +local table = require 'utils.table' local error = error local concat = table.concat @@ -25,6 +26,28 @@ function Public.equal(a, b, optional_message) error(message, 2) end +function Public.is_nil(value, optional_message) + if value == nil then + return + end + + local message = {tostring(value), ' was not nil'} + if optional_message then + message[#message + 1] = ' - ' + message[#message + 1] = optional_message + end + + message = concat(message) + error(message, 2) +end + +function Public.table_equal(a, b) + -- Todo write own table equal + if not table.equals(a, b) then + error('tables not equal', 2) + end +end + function Public.is_true(condition, optional_message) if not condition then error(optional_message or 'condition was not true', 2) diff --git a/utils/test/context.lua b/utils/test/context.lua index 89994c7f..a49fd62c 100644 --- a/utils/test/context.lua +++ b/utils/test/context.lua @@ -2,7 +2,7 @@ local Public = {} Public.__index = Public function Public.new(player) - return setmetatable({player = player, _steps = {}}, Public) + return setmetatable({player = player, _steps = {}, _teardowns = {}}, Public) end function Public.timeout(self, delay, func) @@ -15,4 +15,15 @@ function Public.next(self, func) return self:timeout(1, func) end +function Public.wait(self, delay) + return self:timeout(delay, function() + end) +end + +function Public.add_teardown(self, func) + local teardowns = self._teardowns + teardowns[#teardowns + 1] = func + return self +end + return Public diff --git a/utils/test/event_factory.lua b/utils/test/event_factory.lua index ff9bf82c..913a2866 100644 --- a/utils/test/event_factory.lua +++ b/utils/test/event_factory.lua @@ -57,18 +57,20 @@ function Public.on_player_deconstructed_area(player_index, surface, area, item) end function Public.do_player_deconstruct_area(cursor, player, area, optional_skip_fog_of_war) - cursor.deconstruct_area( - { - surface = player.surface, - force = player.force, - area = area, - by_player = player, - skip_fog_of_war = optional_skip_fog_of_war - } - ) + cursor.deconstruct_area({ + surface = player.surface, + force = player.force, + area = area, + by_player = player, + skip_fog_of_war = optional_skip_fog_of_war + }) local event = Public.on_player_deconstructed_area(player.index, player.surface, area, cursor.name) Public.raise(event) end +function Public.on_player_died(player_index) + return {name = defines.events.on_player_died, tick = game.tick, player_index = player_index, cause = nils} +end + return Public diff --git a/utils/test/helper.lua b/utils/test/helper.lua index a7ccfc45..6a6b01c8 100644 --- a/utils/test/helper.lua +++ b/utils/test/helper.lua @@ -4,12 +4,9 @@ local Public = {} local surface_count = 0 -Global.register( - {surface_count = surface_count}, - function(tbl) - surface_count = tbl.surface_count - end -) +Global.register({surface_count = surface_count}, function(tbl) + surface_count = tbl.surface_count +end) local function get_surface_name() surface_count = surface_count + 1 @@ -17,62 +14,21 @@ local function get_surface_name() end local autoplace_settings = { - tile = { - treat_missing_as_default = false, - settings = { - ['grass-1'] = {frequency = 1, size = 1, richness = 1} - } - } + tile = {treat_missing_as_default = false, settings = {['grass-1'] = {frequency = 1, size = 1, richness = 1}}} } local autoplace_controls = { - trees = { - frequency = 1, - richness = 1, - size = 0 - }, - ['enemy-base'] = { - frequency = 1, - richness = 1, - size = 0 - }, - coal = { - frequency = 1, - richness = 1, - size = 0 - }, - ['copper-ore'] = { - frequency = 1, - richness = 1, - size = 0 - }, - ['crude-oil'] = { - frequency = 1, - richness = 1, - size = 0 - }, - ['iron-ore'] = { - frequency = 1, - richness = 1, - size = 0 - }, - stone = { - frequency = 1, - richness = 1, - size = 0 - }, - ['uranium-ore'] = { - frequency = 1, - richness = 1, - size = 0 - } + trees = {frequency = 1, richness = 1, size = 0}, + ['enemy-base'] = {frequency = 1, richness = 1, size = 0}, + coal = {frequency = 1, richness = 1, size = 0}, + ['copper-ore'] = {frequency = 1, richness = 1, size = 0}, + ['crude-oil'] = {frequency = 1, richness = 1, size = 0}, + ['iron-ore'] = {frequency = 1, richness = 1, size = 0}, + stone = {frequency = 1, richness = 1, size = 0}, + ['uranium-ore'] = {frequency = 1, richness = 1, size = 0} } -local cliff_settings = { - cliff_elevation_0 = 1024, - cliff_elevation_interval = 10, - name = 'cliff' -} +local cliff_settings = {cliff_elevation_0 = 1024, cliff_elevation_interval = 10, name = 'cliff'} function Public.startup_test_surface(context, options) options = options or {} @@ -84,34 +40,29 @@ function Public.startup_test_surface(context, options) local old_position = player.position local old_character = player.character - local surface = - game.create_surface( - name, - { - width = area.x or area[1], - height = area.y or area[2], - autoplace_settings = autoplace_settings, - autoplace_controls = autoplace_controls, - cliff_settings = cliff_settings - } - ) + local surface = game.create_surface(name, { + width = area.x or area[1], + height = area.y or area[2], + autoplace_settings = autoplace_settings, + autoplace_controls = autoplace_controls, + cliff_settings = cliff_settings + }) surface.request_to_generate_chunks({0, 0}, 32) surface.force_generate_chunk_requests() + player.force.chart_all(surface) - context:next( - function() - for k, v in pairs(surface.find_entities()) do - v.destroy() - end - - surface.destroy_decoratives {area = {{-32, -32}, {32, 32}}} - - player.character = nil - player.teleport({0, 0}, surface) - player.create_character() + context:next(function() + for k, v in pairs(surface.find_entities()) do + v.destroy() end - ) + + surface.destroy_decoratives {area = {{-32, -32}, {32, 32}}} + + player.character = nil + player.teleport({0, 0}, surface) + player.create_character() + end) return function() player.character = nil @@ -125,4 +76,26 @@ function Public.startup_test_surface(context, options) end end +function Public.wait_for_chunk_to_be_charted(context, force, surface, chunk_position, next) + if not force.is_chunk_charted(surface, chunk_position) then + context:next(function() + Public.wait_for_chunk_to_be_charted(context, force, surface, chunk_position) + end) + return + end + + if next then + context:next(next) + end +end + +function Public.modify_lua_object(context, object, key, value) + local old_value = object[key] + rawset(object, key, value) + + context:add_teardown(function() + rawset(object, key, old_value) + end) +end + return Public diff --git a/utils/test/runner.lua b/utils/test/runner.lua index 95fe1b40..00b5f155 100644 --- a/utils/test/runner.lua +++ b/utils/test/runner.lua @@ -8,9 +8,7 @@ local pcall = pcall local Public = {} -Public.events = { - tests_run_finished = Event.generate_event_name('test_run_finished') -} +Public.events = {tests_run_finished = Event.generate_event_name('test_run_finished')} local run_runnables_token @@ -67,6 +65,10 @@ local function print_hook_error(hook) hook.context.player.print(table.concat {'Failed ', hook.name, " hook -':", tostring(hook.error)}, {r = 1}) end +local function print_teardown_error(context, name, error_message) + context.player.print(table.concat {'Failed ', name, " teardown -':", error_message}, {r = 1}) +end + local function record_hook_error_in_module(hook) if hook.name == 'startup' then hook.module.startup_error = hook.error @@ -86,6 +88,32 @@ local function do_termination(data) return true end +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 + local function run_hook(hook) local context = hook.context local steps = context._steps @@ -104,14 +132,22 @@ local function run_hook(hook) hook.error = return_value print_hook_error(hook) record_hook_error_in_module(hook) + do_teardowns(context, hook.name) return false end - if current_step == #steps then - return true + if current_step ~= #steps then + return nil end - return nil + 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 end local function do_hook(hook, data) @@ -152,16 +188,24 @@ local function run_test(test) print_error(context.player, test.name, return_value) test.passed = false test.error = return_value + do_teardowns(context, test.name) return false end - if current_step == #steps then - print_success(context.player, test.name) - test.passed = true - return true + if current_step ~= #steps then + return nil end - return nil + 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 end local function do_test(test, data) @@ -219,16 +263,14 @@ end local function run(runnables, player, options) options = validate_options(options) - run_runnables( - { - runnables = runnables, - player = player, - index = 1, - count = 0, - fail_count = 0, - stop_on_first_error = options.stop_on_first_error - } - ) + run_runnables({ + runnables = runnables, + player = player, + index = 1, + count = 0, + fail_count = 0, + stop_on_first_error = options.stop_on_first_error + }) end function Public.run_module(module, player, options)