diff --git a/scripts/data/integration_tests/test_lua_api/global.lua b/scripts/data/integration_tests/test_lua_api/global.lua index cc8240554a..225660b858 100644 --- a/scripts/data/integration_tests/test_lua_api/global.lua +++ b/scripts/data/integration_tests/test_lua_api/global.lua @@ -326,6 +326,24 @@ testing.registerGlobalTest('player weapon attack', function() testing.runLocalTest(player, 'player weapon attack') end) +testing.registerGlobalTest('load while teleporting - init player', function() + local player = world.players[1] + player:teleport('Museum of Wonders', util.vector3(0, -1500, 111), util.transform.rotateZ(math.rad(180))) +end) + +testing.registerGlobalTest('load while teleporting - teleport', function() + local player = world.players[1] + local landracer = world.createObject('landracer') + landracer:teleport(player.cell, player.position + util.vector3(0, 500, 0)) + coroutine.yield() + + local door = world.getObjectByFormId(core.getFormId('the_hub.omwaddon', 26)) + door:activateBy(player) + coroutine.yield() + + landracer:teleport(player.cell, player.position) +end) + return { engineHandlers = { onUpdate = testing.updateGlobal, diff --git a/scripts/data/integration_tests/test_lua_api/menu.lua b/scripts/data/integration_tests/test_lua_api/menu.lua index 37f7cb826e..8c6895a5d8 100644 --- a/scripts/data/integration_tests/test_lua_api/menu.lua +++ b/scripts/data/integration_tests/test_lua_api/menu.lua @@ -1,12 +1,63 @@ local testing = require('testing_util') +local matchers = require('matchers') local menu = require('openmw.menu') +testing.registerMenuTest('save and load', function() + menu.newGame() + coroutine.yield() + menu.saveGame('save and load') + coroutine.yield() + + local directorySaves = {} + directorySaves['save_and_load.omwsave'] = { + playerName = '', + playerLevel = 1, + timePlayed = 0, + description = 'save and load', + contentFiles = { + 'builtin.omwscripts', + 'template.omwgame', + 'landracer.omwaddon', + 'the_hub.omwaddon', + 'test_lua_api.omwscripts', + }, + creationTime = matchers.isAny(), + } + local expectedAllSaves = {} + expectedAllSaves[' - 1'] = directorySaves + + testing.expectThat(menu.getAllSaves(), matchers.equalTo(expectedAllSaves)) + + menu.loadGame(' - 1', 'save_and_load.omwsave') + coroutine.yield() + + menu.deleteGame(' - 1', 'save_and_load.omwsave') + testing.expectThat(menu.getAllSaves(), matchers.equalTo({})) +end) + +testing.registerMenuTest('load while teleporting', function() + menu.newGame() + coroutine.yield() + + testing.runGlobalTest('load while teleporting - init player') + + menu.saveGame('load while teleporting') + coroutine.yield() + + testing.runGlobalTest('load while teleporting - teleport') + + menu.loadGame(' - 1', 'load_while_teleporting.omwsave') + coroutine.yield() + + menu.deleteGame(' - 1', 'load_while_teleporting.omwsave') +end) + local function registerGlobalTest(name, description) - testing.registerMenuTest(description or name, function() - menu.newGame() - coroutine.yield() - testing.runGlobalTest(name) - end) + testing.registerMenuTest(description or name, function() + menu.newGame() + coroutine.yield() + testing.runGlobalTest(name) + end) end registerGlobalTest('timers') diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua index 74769da8e0..0b481fba2b 100644 --- a/scripts/data/integration_tests/test_lua_api/player.lua +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -6,6 +6,7 @@ local input = require('openmw.input') local types = require('openmw.types') local nearby = require('openmw.nearby') local camera = require('openmw.camera') +local matchers = require('matchers') types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Controls, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false) @@ -113,13 +114,13 @@ testing.registerLocalTest('player rotation', coroutine.yield() local alpha1, gamma1 = self.rotation:getAnglesXZ() - testing.expectThat(alpha1, testing.isNotNan(), 'Alpha rotation in XZ convention is nan') - testing.expectThat(gamma1, testing.isNotNan(), 'Gamma rotation in XZ convention is nan') + testing.expectThat(alpha1, matchers.isNotNan(), 'Alpha rotation in XZ convention is nan') + testing.expectThat(gamma1, matchers.isNotNan(), 'Gamma rotation in XZ convention is nan') local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX() - testing.expectThat(alpha2, testing.isNotNan(), 'Alpha rotation in ZYX convention is nan') - testing.expectThat(beta2, testing.isNotNan(), 'Beta rotation in ZYX convention is nan') - testing.expectThat(gamma2, testing.isNotNan(), 'Gamma rotation in ZYX convention is nan') + testing.expectThat(alpha2, matchers.isNotNan(), 'Alpha rotation in ZYX convention is nan') + testing.expectThat(beta2, matchers.isNotNan(), 'Beta rotation in ZYX convention is nan') + testing.expectThat(gamma2, matchers.isNotNan(), 'Gamma rotation in ZYX convention is nan') end end) diff --git a/scripts/data/integration_tests/testing_util/matchers.lua b/scripts/data/integration_tests/testing_util/matchers.lua new file mode 100644 index 0000000000..c7643af206 --- /dev/null +++ b/scripts/data/integration_tests/testing_util/matchers.lua @@ -0,0 +1,218 @@ +local module = {} + +--- +-- Matcher verifying that distance between given value and expected is not greater than maxDistance. +-- @function elementsAreArray +-- @param expected#vector. +-- @usage +-- expectThat(util.vector2(0, 0), closeToVector(util.vector2(0, 1), 1)) +function module.closeToVector(expected, maxDistance) + return function(actual) + local distance = (expected - actual):length() + if distance <= maxDistance then + return '' + end + return string.format('%s is too far from expected %s: %s > %s', actual, expected, distance, maxDistance) + end +end + +--- +-- Matcher verifying that given value is an array each element of which matches elements of expected. +-- @function elementsAreArray +-- @param expected#array of values or matcher functions. +-- @usage +-- local t = {42, 13} +-- local matcher = function(actual) +-- if actual ~= 42 then +-- return string.format('%s is not 42', actual) +-- end +-- return '' +-- end +-- expectThat({42, 13}, elementsAreArray({matcher, 13})) +function module.elementsAreArray(expected) + local expected_matchers = {} + for i, v in ipairs(expected) do + if type(v) == 'function' then + expected_matchers[i] = v + else + expected_matchers[i] = function (other) + if expected[i].__eq(expected[i], other) then + return '' + end + return string.format('%s element %s does no match expected: %s', i, other, expected[i]) + end + end + end + return function(actual) + if #actual < #expected_matchers then + return string.format('number of elements is less than expected: %s < %s', #actual, #expected_matchers) + end + local message = '' + for i, v in ipairs(actual) do + if i > #expected_matchers then + message = string.format('%s\n%s element is out of expected range: %s', message, i, #expected_matchers) + break + end + local match_message = expected_matchers[i](v) + if match_message ~= '' then + message = string.format('%s\n%s', message, match_message) + end + end + return message + end +end + +--- +-- Matcher verifying that given number is not a nan. +-- @function isNotNan +-- @usage +-- expectThat(value, isNotNan()) +function module.isNotNan() + return function(actual) + if actual ~= actual then + return 'actual value is nan, expected to be not nan' + end + return '' + end +end + +--- +-- Matcher accepting any value. +-- @function isAny +-- @usage +-- expectThat(value, isAny()) +function module.isAny() + return function(actual) + return '' + end +end + +local function serializeArray(a) + local result = nil + for _, v in ipairs(a) do + if result == nil then + result = string.format('{%s', serialize(v)) + else + result = string.format('%s, %s', result, serialize(v)) + end + end + if result == nil then + return '{}' + end + return string.format('%s}', result) +end + +local function serializeTable(t) + local result = nil + for k, v in pairs(t) do + if result == nil then + result = string.format('{%q = %s', k, serialize(v)) + else + result = string.format('%s, %q = %s', result, k, serialize(v)) + end + end + if result == nil then + return '{}' + end + return string.format('%s}', result) +end + +local function isArray(t) + local i = 1 + for _ in pairs(t) do + if t[i] == nil then + return false + end + i = i + 1 + end + return true +end + +function serialize(v) + local t = type(v) + if t == 'string' then + return string.format('%q', v) + elseif t == 'table' then + if isArray(v) then + return serializeArray(v) + end + return serializeTable(v) + end + return string.format('%s', v) +end + +local function compareScalars(v1, v2) + if v1 == v2 then + return '' + end + if type(v1) == 'string' then + return string.format('%q ~= %q', v1, v2) + end + return string.format('%s ~= %s', v1, v2) +end + +local function collectKeys(t) + local result = {} + for key in pairs(t) do + table.insert(result, key) + end + table.sort(result) + return result +end + +local function compareTables(t1, t2) + local keys1 = collectKeys(t1) + local keys2 = collectKeys(t2) + if #keys1 ~= #keys2 then + return string.format('table size mismatch: %d ~= %d', #keys1, #keys2) + end + for i = 1, #keys1 do + local key1 = keys1[i] + local key2 = keys2[i] + if key1 ~= key2 then + return string.format('table keys mismatch: %q ~= %q', key1, key2) + end + local d = compare(t1[key1], t2[key2]) + if d ~= '' then + return string.format('table values mismatch at key %s: %s', serialize(key1), d) + end + end + return '' +end + +function compare(v1, v2) + local type1 = type(v1) + local type2 = type(v2) + if type2 == 'function' then + return v2(v1) + end + if type1 ~= type2 then + return string.format('types mismatch: %s ~= %s', type1, type2) + end + if type1 == 'nil' then + return '' + elseif type1 == 'table' then + return compareTables(v1, v2) + elseif type1 == 'nil' or type1 == 'boolean' or type1 == 'number' or type1 == 'string' then + return compareScalars(v1, v2) + end + error('unsupported type: %s', type1) +end + +--- +-- Matcher verifying that given value is equal to expected. Accepts nil, boolean, number, string and table or matcher +-- function. +-- @function equalTo +-- @usage +-- expectThat({a = {42, 'foo', {b = true}}}, equalTo({a = {42, 'foo', {b = true}}})) +function module.equalTo(expected) + return function(actual) + local diff = compare(actual, expected) + if diff == '' then + return '' + end + return string.format('%s; actual: %s; expected: %s', diff, serialize(actual, ''), serialize(expected, '')) + end +end + +return module diff --git a/scripts/data/integration_tests/testing_util/testing_util.lua b/scripts/data/integration_tests/testing_util/testing_util.lua index 5a69cb89ed..c894fa96f4 100644 --- a/scripts/data/integration_tests/testing_util/testing_util.lua +++ b/scripts/data/integration_tests/testing_util/testing_util.lua @@ -158,76 +158,6 @@ function M.expectNotEqual(v1, v2, msg) end end -function M.closeToVector(expected, maxDistance) - return function(actual) - local distance = (expected - actual):length() - if distance <= maxDistance then - return '' - end - return string.format('%s is too far from expected %s: %s > %s', actual, expected, distance, maxDistance) - end -end - ---- --- Matcher verifying that given value is an array each element of which matches elements of expected. --- @function elementsAreArray --- @param expected#array of values or matcher functions. --- @usage --- local t = {42, 13} --- local matcher = function(actual) --- if actual ~= 42 then --- return string.format('%s is not 42', actual) --- end --- return '' --- end --- expectThat({42, 13}, elementsAreArray({matcher, 13})) -function M.elementsAreArray(expected) - local expected_matchers = {} - for i, v in ipairs(expected) do - if type(v) == 'function' then - expected_matchers[i] = v - else - expected_matchers[i] = function (other) - if expected[i].__eq(expected[i], other) then - return '' - end - return string.format('%s element %s does no match expected: %s', i, other, expected[i]) - end - end - end - return function(actual) - if #actual < #expected_matchers then - return string.format('number of elements is less than expected: %s < %s', #actual, #expected_matchers) - end - local message = '' - for i, v in ipairs(actual) do - if i > #expected_matchers then - message = string.format('%s\n%s element is out of expected range: %s', message, i, #expected_matchers) - break - end - local match_message = expected_matchers[i](v) - if match_message ~= '' then - message = string.format('%s\n%s', message, match_message) - end - end - return message - end -end - ---- --- Matcher verifying that given number is not a nan. --- @function isNotNan --- @usage --- expectThat(value, isNotNan()) -function M.isNotNan(expected) - return function(actual) - if actual ~= actual then - return 'actual value is nan, expected to be not nan' - end - return '' - end -end - --- -- Verifies that given value matches provided matcher. -- @function expectThat diff --git a/scripts/data/morrowind_tests/player.lua b/scripts/data/morrowind_tests/player.lua index 226d9754f0..fcb1126c1b 100644 --- a/scripts/data/morrowind_tests/player.lua +++ b/scripts/data/morrowind_tests/player.lua @@ -5,6 +5,7 @@ local testing = require('testing_util') local util = require('openmw.util') local types = require('openmw.types') local nearby = require('openmw.nearby') +local matchers = require('matchers') types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Jumping, false) @@ -45,33 +46,33 @@ testing.registerLocalTest('Guard in Imperial Prison Ship should find path (#7241 testing.expectLessOrEqual((util.vector2(path[#path].x, path[#path].y) - util.vector2(dst.x, dst.y)):length(), 1, 'Last path point x, y') testing.expectLessOrEqual(path[#path].z - dst.z, 20, 'Last path point z') if agentBounds.shapeType == nearby.COLLISION_SHAPE_TYPE.Aabb then - testing.expectThat(path, testing.elementsAreArray({ - testing.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), - testing.closeToVector(util.vector3(15, 1102, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-112, 1110, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-67.99993896484375, 1421.2000732421875, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-33.999935150146484375, 1414.4000244140625, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-6.79993534088134765625, 1380.4000244140625, 85.094573974609375), 1e-1), - testing.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), - testing.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), - testing.closeToVector(util.vector3(83.552001953125, 42.26399993896484375, -104.58989715576171875), 1e-1), - testing.closeToVector(util.vector3(89, -105, -98.72841644287109375), 1e-1), - testing.closeToVector(util.vector3(90, -90, -99.7056884765625), 1e-1), + testing.expectThat(path, matchers.elementsAreArray({ + matchers.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), + matchers.closeToVector(util.vector3(15, 1102, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-112, 1110, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-67.99993896484375, 1421.2000732421875, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-33.999935150146484375, 1414.4000244140625, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-6.79993534088134765625, 1380.4000244140625, 85.094573974609375), 1e-1), + matchers.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), + matchers.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), + matchers.closeToVector(util.vector3(83.552001953125, 42.26399993896484375, -104.58989715576171875), 1e-1), + matchers.closeToVector(util.vector3(89, -105, -98.72841644287109375), 1e-1), + matchers.closeToVector(util.vector3(90, -90, -99.7056884765625), 1e-1), })) elseif agentBounds.shapeType == nearby.COLLISION_SHAPE_TYPE.Cylinder then - testing.expectThat(path, testing.elementsAreArray({ - testing.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), - testing.closeToVector(util.vector3(-13.5999355316162109375, 1060.800048828125, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-27.1999359130859375, 1081.2000732421875, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-81.59993743896484375, 1128.800048828125, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-101.99993896484375, 1156.0001220703125, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), - testing.closeToVector(util.vector3(7, 1470, 114.73973846435546875), 1e-1), - testing.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), - testing.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), - testing.closeToVector(util.vector3(95, 27, -104.83390045166015625), 1e-1), - testing.closeToVector(util.vector3(90, -90, -104.83390045166015625), 1e-1), + testing.expectThat(path, matchers.elementsAreArray({ + matchers.closeToVector(util.vector3(34.29737091064453125, 806.3817138671875, 112.76610565185546875), 1e-1), + matchers.closeToVector(util.vector3(-13.5999355316162109375, 1060.800048828125, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-27.1999359130859375, 1081.2000732421875, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-81.59993743896484375, 1128.800048828125, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-101.99993896484375, 1156.0001220703125, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(-118, 1393, 112.2945709228515625), 1e-1), + matchers.closeToVector(util.vector3(7, 1470, 114.73973846435546875), 1e-1), + matchers.closeToVector(util.vector3(79, 724, -104.83390045166015625), 1e-1), + matchers.closeToVector(util.vector3(84, 290.000030517578125, -104.83390045166015625), 1e-1), + matchers.closeToVector(util.vector3(95, 27, -104.83390045166015625), 1e-1), + matchers.closeToVector(util.vector3(90, -90, -104.83390045166015625), 1e-1), })) end end)