Skip to content

Commit 72aefbf

Browse files
committed
Merge branch 'lua_save_load_test' into 'master'
Add Lua integration tests for loading and saving See merge request OpenMW/openmw!4604
2 parents 747771a + 2ebdc43 commit 72aefbf

File tree

6 files changed

+324
-105
lines changed

6 files changed

+324
-105
lines changed

scripts/data/integration_tests/test_lua_api/global.lua

+18
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,24 @@ testing.registerGlobalTest('player weapon attack', function()
326326
testing.runLocalTest(player, 'player weapon attack')
327327
end)
328328

329+
testing.registerGlobalTest('load while teleporting - init player', function()
330+
local player = world.players[1]
331+
player:teleport('Museum of Wonders', util.vector3(0, -1500, 111), util.transform.rotateZ(math.rad(180)))
332+
end)
333+
334+
testing.registerGlobalTest('load while teleporting - teleport', function()
335+
local player = world.players[1]
336+
local landracer = world.createObject('landracer')
337+
landracer:teleport(player.cell, player.position + util.vector3(0, 500, 0))
338+
coroutine.yield()
339+
340+
local door = world.getObjectByFormId(core.getFormId('the_hub.omwaddon', 26))
341+
door:activateBy(player)
342+
coroutine.yield()
343+
344+
landracer:teleport(player.cell, player.position)
345+
end)
346+
329347
return {
330348
engineHandlers = {
331349
onUpdate = testing.updateGlobal,

scripts/data/integration_tests/test_lua_api/menu.lua

+56-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,63 @@
11
local testing = require('testing_util')
2+
local matchers = require('matchers')
23
local menu = require('openmw.menu')
34

5+
testing.registerMenuTest('save and load', function()
6+
menu.newGame()
7+
coroutine.yield()
8+
menu.saveGame('save and load')
9+
coroutine.yield()
10+
11+
local directorySaves = {}
12+
directorySaves['save_and_load.omwsave'] = {
13+
playerName = '',
14+
playerLevel = 1,
15+
timePlayed = 0,
16+
description = 'save and load',
17+
contentFiles = {
18+
'builtin.omwscripts',
19+
'template.omwgame',
20+
'landracer.omwaddon',
21+
'the_hub.omwaddon',
22+
'test_lua_api.omwscripts',
23+
},
24+
creationTime = matchers.isAny(),
25+
}
26+
local expectedAllSaves = {}
27+
expectedAllSaves[' - 1'] = directorySaves
28+
29+
testing.expectThat(menu.getAllSaves(), matchers.equalTo(expectedAllSaves))
30+
31+
menu.loadGame(' - 1', 'save_and_load.omwsave')
32+
coroutine.yield()
33+
34+
menu.deleteGame(' - 1', 'save_and_load.omwsave')
35+
testing.expectThat(menu.getAllSaves(), matchers.equalTo({}))
36+
end)
37+
38+
testing.registerMenuTest('load while teleporting', function()
39+
menu.newGame()
40+
coroutine.yield()
41+
42+
testing.runGlobalTest('load while teleporting - init player')
43+
44+
menu.saveGame('load while teleporting')
45+
coroutine.yield()
46+
47+
testing.runGlobalTest('load while teleporting - teleport')
48+
49+
menu.loadGame(' - 1', 'load_while_teleporting.omwsave')
50+
coroutine.yield()
51+
52+
menu.deleteGame(' - 1', 'load_while_teleporting.omwsave')
53+
end)
54+
455
local function registerGlobalTest(name, description)
5-
testing.registerMenuTest(description or name, function()
6-
menu.newGame()
7-
coroutine.yield()
8-
testing.runGlobalTest(name)
9-
end)
56+
testing.registerMenuTest(description or name, function()
57+
menu.newGame()
58+
coroutine.yield()
59+
testing.runGlobalTest(name)
60+
end)
1061
end
1162

1263
registerGlobalTest('timers')

scripts/data/integration_tests/test_lua_api/player.lua

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local input = require('openmw.input')
66
local types = require('openmw.types')
77
local nearby = require('openmw.nearby')
88
local camera = require('openmw.camera')
9+
local matchers = require('matchers')
910

1011
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Controls, false)
1112
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false)
@@ -113,13 +114,13 @@ testing.registerLocalTest('player rotation',
113114
coroutine.yield()
114115

115116
local alpha1, gamma1 = self.rotation:getAnglesXZ()
116-
testing.expectThat(alpha1, testing.isNotNan(), 'Alpha rotation in XZ convention is nan')
117-
testing.expectThat(gamma1, testing.isNotNan(), 'Gamma rotation in XZ convention is nan')
117+
testing.expectThat(alpha1, matchers.isNotNan(), 'Alpha rotation in XZ convention is nan')
118+
testing.expectThat(gamma1, matchers.isNotNan(), 'Gamma rotation in XZ convention is nan')
118119

119120
local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX()
120-
testing.expectThat(alpha2, testing.isNotNan(), 'Alpha rotation in ZYX convention is nan')
121-
testing.expectThat(beta2, testing.isNotNan(), 'Beta rotation in ZYX convention is nan')
122-
testing.expectThat(gamma2, testing.isNotNan(), 'Gamma rotation in ZYX convention is nan')
121+
testing.expectThat(alpha2, matchers.isNotNan(), 'Alpha rotation in ZYX convention is nan')
122+
testing.expectThat(beta2, matchers.isNotNan(), 'Beta rotation in ZYX convention is nan')
123+
testing.expectThat(gamma2, matchers.isNotNan(), 'Gamma rotation in ZYX convention is nan')
123124
end
124125
end)
125126

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
local module = {}
2+
3+
---
4+
-- Matcher verifying that distance between given value and expected is not greater than maxDistance.
5+
-- @function elementsAreArray
6+
-- @param expected#vector.
7+
-- @usage
8+
-- expectThat(util.vector2(0, 0), closeToVector(util.vector2(0, 1), 1))
9+
function module.closeToVector(expected, maxDistance)
10+
return function(actual)
11+
local distance = (expected - actual):length()
12+
if distance <= maxDistance then
13+
return ''
14+
end
15+
return string.format('%s is too far from expected %s: %s > %s', actual, expected, distance, maxDistance)
16+
end
17+
end
18+
19+
---
20+
-- Matcher verifying that given value is an array each element of which matches elements of expected.
21+
-- @function elementsAreArray
22+
-- @param expected#array of values or matcher functions.
23+
-- @usage
24+
-- local t = {42, 13}
25+
-- local matcher = function(actual)
26+
-- if actual ~= 42 then
27+
-- return string.format('%s is not 42', actual)
28+
-- end
29+
-- return ''
30+
-- end
31+
-- expectThat({42, 13}, elementsAreArray({matcher, 13}))
32+
function module.elementsAreArray(expected)
33+
local expected_matchers = {}
34+
for i, v in ipairs(expected) do
35+
if type(v) == 'function' then
36+
expected_matchers[i] = v
37+
else
38+
expected_matchers[i] = function (other)
39+
if expected[i].__eq(expected[i], other) then
40+
return ''
41+
end
42+
return string.format('%s element %s does no match expected: %s', i, other, expected[i])
43+
end
44+
end
45+
end
46+
return function(actual)
47+
if #actual < #expected_matchers then
48+
return string.format('number of elements is less than expected: %s < %s', #actual, #expected_matchers)
49+
end
50+
local message = ''
51+
for i, v in ipairs(actual) do
52+
if i > #expected_matchers then
53+
message = string.format('%s\n%s element is out of expected range: %s', message, i, #expected_matchers)
54+
break
55+
end
56+
local match_message = expected_matchers[i](v)
57+
if match_message ~= '' then
58+
message = string.format('%s\n%s', message, match_message)
59+
end
60+
end
61+
return message
62+
end
63+
end
64+
65+
---
66+
-- Matcher verifying that given number is not a nan.
67+
-- @function isNotNan
68+
-- @usage
69+
-- expectThat(value, isNotNan())
70+
function module.isNotNan()
71+
return function(actual)
72+
if actual ~= actual then
73+
return 'actual value is nan, expected to be not nan'
74+
end
75+
return ''
76+
end
77+
end
78+
79+
---
80+
-- Matcher accepting any value.
81+
-- @function isAny
82+
-- @usage
83+
-- expectThat(value, isAny())
84+
function module.isAny()
85+
return function(actual)
86+
return ''
87+
end
88+
end
89+
90+
local function serializeArray(a)
91+
local result = nil
92+
for _, v in ipairs(a) do
93+
if result == nil then
94+
result = string.format('{%s', serialize(v))
95+
else
96+
result = string.format('%s, %s', result, serialize(v))
97+
end
98+
end
99+
if result == nil then
100+
return '{}'
101+
end
102+
return string.format('%s}', result)
103+
end
104+
105+
local function serializeTable(t)
106+
local result = nil
107+
for k, v in pairs(t) do
108+
if result == nil then
109+
result = string.format('{%q = %s', k, serialize(v))
110+
else
111+
result = string.format('%s, %q = %s', result, k, serialize(v))
112+
end
113+
end
114+
if result == nil then
115+
return '{}'
116+
end
117+
return string.format('%s}', result)
118+
end
119+
120+
local function isArray(t)
121+
local i = 1
122+
for _ in pairs(t) do
123+
if t[i] == nil then
124+
return false
125+
end
126+
i = i + 1
127+
end
128+
return true
129+
end
130+
131+
function serialize(v)
132+
local t = type(v)
133+
if t == 'string' then
134+
return string.format('%q', v)
135+
elseif t == 'table' then
136+
if isArray(v) then
137+
return serializeArray(v)
138+
end
139+
return serializeTable(v)
140+
end
141+
return string.format('%s', v)
142+
end
143+
144+
local function compareScalars(v1, v2)
145+
if v1 == v2 then
146+
return ''
147+
end
148+
if type(v1) == 'string' then
149+
return string.format('%q ~= %q', v1, v2)
150+
end
151+
return string.format('%s ~= %s', v1, v2)
152+
end
153+
154+
local function collectKeys(t)
155+
local result = {}
156+
for key in pairs(t) do
157+
table.insert(result, key)
158+
end
159+
table.sort(result)
160+
return result
161+
end
162+
163+
local function compareTables(t1, t2)
164+
local keys1 = collectKeys(t1)
165+
local keys2 = collectKeys(t2)
166+
if #keys1 ~= #keys2 then
167+
return string.format('table size mismatch: %d ~= %d', #keys1, #keys2)
168+
end
169+
for i = 1, #keys1 do
170+
local key1 = keys1[i]
171+
local key2 = keys2[i]
172+
if key1 ~= key2 then
173+
return string.format('table keys mismatch: %q ~= %q', key1, key2)
174+
end
175+
local d = compare(t1[key1], t2[key2])
176+
if d ~= '' then
177+
return string.format('table values mismatch at key %s: %s', serialize(key1), d)
178+
end
179+
end
180+
return ''
181+
end
182+
183+
function compare(v1, v2)
184+
local type1 = type(v1)
185+
local type2 = type(v2)
186+
if type2 == 'function' then
187+
return v2(v1)
188+
end
189+
if type1 ~= type2 then
190+
return string.format('types mismatch: %s ~= %s', type1, type2)
191+
end
192+
if type1 == 'nil' then
193+
return ''
194+
elseif type1 == 'table' then
195+
return compareTables(v1, v2)
196+
elseif type1 == 'nil' or type1 == 'boolean' or type1 == 'number' or type1 == 'string' then
197+
return compareScalars(v1, v2)
198+
end
199+
error('unsupported type: %s', type1)
200+
end
201+
202+
---
203+
-- Matcher verifying that given value is equal to expected. Accepts nil, boolean, number, string and table or matcher
204+
-- function.
205+
-- @function equalTo
206+
-- @usage
207+
-- expectThat({a = {42, 'foo', {b = true}}}, equalTo({a = {42, 'foo', {b = true}}}))
208+
function module.equalTo(expected)
209+
return function(actual)
210+
local diff = compare(actual, expected)
211+
if diff == '' then
212+
return ''
213+
end
214+
return string.format('%s; actual: %s; expected: %s', diff, serialize(actual, ''), serialize(expected, ''))
215+
end
216+
end
217+
218+
return module

0 commit comments

Comments
 (0)