Register global tests to run them

This commit is contained in:
elsid 2025-02-26 23:12:17 +01:00
parent 8b62f02523
commit 981ca957c1
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40
6 changed files with 181 additions and 224 deletions

View File

@ -7,7 +7,7 @@ local vfs = require('openmw.vfs')
local world = require('openmw.world')
local I = require('openmw.interfaces')
local function testTimers()
testing.registerGlobalTest('testTimers', function()
testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result')
testing.expectAlmostEqual(core.getSimulationTimeScale(), 1, 'incorrect getSimulationTimeScale result')
@ -39,9 +39,9 @@ local function testTimers()
testing.expectGreaterOrEqual(ts1, 0.5, 'async:newSimulationTimer failed')
testing.expectGreaterOrEqual(th2, 72, 'async:newUnsavableGameTimer failed')
testing.expectGreaterOrEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed')
end
end)
local function testTeleport()
testing.registerGlobalTest('testTeleport', function()
local player = world.players[1]
player:teleport('', util.vector3(100, 50, 500), util.transform.rotateZ(math.rad(90)))
coroutine.yield()
@ -72,16 +72,16 @@ local function testTeleport()
testing.expectEqualWithDelta(player.position.x, 50, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.position.y, -100, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(-90), 0.05, 'teleporting changes rotation')
end
end)
local function testGetGMST()
testing.registerGlobalTest('testGetGMST', function()
testing.expectEqual(core.getGMST('non-existed gmst'), nil)
testing.expectEqual(core.getGMST('Water_RippleFrameCount'), 4)
testing.expectEqual(core.getGMST('Inventory_DirectionalDiffuseR'), 0.5)
testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something')
end
end)
local function testMWScript()
testing.registerGlobalTest('testMWScript', function()
local variableStoreCount = 18
local variableStore = world.mwscript.getGlobalVariables(player)
testing.expectEqual(variableStoreCount, #variableStore)
@ -101,7 +101,7 @@ local function testMWScript()
indexCheck = indexCheck + 1
end
testing.expectEqual(variableStoreCount, indexCheck)
end
end)
local function testRecordStore(store, storeName, skipPairs)
testing.expect(store.records)
@ -122,7 +122,7 @@ local function testRecordStore(store, storeName, skipPairs)
testing.expectEqual(status, true, storeName)
end
local function testRecordStores()
testing.registerGlobalTest('testRecordStores', function()
for key, type in pairs(types) do
if type.records then
testRecordStore(type, key)
@ -141,9 +141,9 @@ local function testRecordStores()
testRecordStore(types.NPC.classes, "classes")
testRecordStore(types.NPC.races, "races")
testRecordStore(types.Player.birthSigns, "birthSigns")
end
end)
local function testRecordCreation()
testing.registerGlobalTest('testRecordCreation', function()
local newLight = {
isCarriable = true,
isDynamic = true,
@ -166,9 +166,9 @@ local function testRecordCreation()
for key, value in pairs(newLight) do
testing.expectEqual(record[key], value)
end
end
end)
local function testUTF8Chars()
testing.registerGlobalTest('testUTF8Chars', function()
testing.expectEqual(utf8.codepoint("😀"), 0x1F600)
local chars = {}
@ -193,9 +193,9 @@ local function testUTF8Chars()
testing.expectEqual(utf8.codepoint(char), codepoint)
testing.expectEqual(utf8.len(char), 1)
end
end
end)
local function testUTF8Strings()
testing.registerGlobalTest('testUTF8Strings', function()
local utf8str = "Hello, 你好, 🌎!"
local str = ""
@ -206,9 +206,9 @@ local function testUTF8Strings()
testing.expectEqual(utf8.len(utf8str), 13)
testing.expectEqual(utf8.offset(utf8str, 9), 11)
end
end)
local function testMemoryLimit()
testing.registerGlobalTest('testMemoryLimit', function()
local ok, err = pcall(function()
local t = {}
local n = 1
@ -219,7 +219,7 @@ local function testMemoryLimit()
end)
testing.expectEqual(ok, false, 'Script reaching memory limit should fail')
testing.expectEqual(err, 'not enough memory')
end
end)
local function initPlayer()
local player = world.players[1]
@ -228,7 +228,7 @@ local function initPlayer()
return player
end
local function testVFS()
testing.registerGlobalTest('testVFS', function()
local file = 'test_vfs_dir/lines.txt'
local nosuchfile = 'test_vfs_dir/nosuchfile'
testing.expectEqual(vfs.fileExists(file), true, 'lines.txt should exist')
@ -272,9 +272,9 @@ local function testVFS()
for _,v in pairs(expectedLines) do
testing.expectEqual(getLine(), v)
end
end
end)
local function testCommitCrime()
testing.registerGlobalTest('testCommitCrime', function()
local player = initPlayer()
testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `testCommitCrime`')
testing.expectEqual(I.Crimes == nil, false, 'Crimes interface should be available in global contexts')
@ -294,80 +294,41 @@ local function testCommitCrime()
types.Player.setCrimeLevel(player, 0)
testing.expectEqual(I.Crimes.commitCrime(player, { victim = victim, type = types.Player.OFFENSE_TYPE.Theft, arg = 50 }).wasCrimeSeen, true, "Running a crime with a valid victim should notify them when the player is not sneaking, even if it's not explicitly passed in")
testing.expectEqual(types.Player.getCrimeLevel(player), 0, "Crime level should not change if the victim's alarm value is low and there's no other witnesses")
end
end)
local function testRecordModelProperty()
testing.registerGlobalTest('testRecordModelProperty', function()
local player = initPlayer()
testing.expectEqual(types.NPC.record(player).model, 'meshes/basicplayer.dae')
end)
local function registerPlayerTest(name)
testing.registerGlobalTest(name, function()
local player = initPlayer()
testing.runLocalTest(player, name)
end)
end
tests = {
{'timers', testTimers},
{'rotating player with controls.yawChange should change rotation', function()
local player = initPlayer()
testing.runLocalTest(player, 'playerYawRotation')
end},
{'rotating player with controls.pitchChange should change rotation', function()
local player = initPlayer()
testing.runLocalTest(player, 'playerPitchRotation')
end},
{'rotating player with controls.pitchChange and controls.yawChange should change rotation', function()
local player = initPlayer()
testing.runLocalTest(player, 'playerPitchAndYawRotation')
end},
{'rotating player should not lead to nan rotation', function()
local player = initPlayer()
testing.runLocalTest(player, 'playerRotation')
end},
{'playerForwardRunning', function()
local player = initPlayer()
testing.runLocalTest(player, 'playerForwardRunning')
end},
{'playerDiagonalWalking', function()
local player = initPlayer()
testing.runLocalTest(player, 'playerDiagonalWalking')
end},
{'findPath', function()
local player = initPlayer()
testing.runLocalTest(player, 'findPath')
end},
{'findRandomPointAroundCircle', function()
local player = initPlayer()
testing.runLocalTest(player, 'findRandomPointAroundCircle')
end},
{'castNavigationRay', function()
local player = initPlayer()
testing.runLocalTest(player, 'castNavigationRay')
end},
{'findNearestNavMeshPosition', function()
local player = initPlayer()
testing.runLocalTest(player, 'findNearestNavMeshPosition')
end},
{'teleport', testTeleport},
{'getGMST', testGetGMST},
{'recordStores', testRecordStores},
{'recordCreation', testRecordCreation},
{'utf8Chars', testUTF8Chars},
{'utf8Strings', testUTF8Strings},
{'mwscript', testMWScript},
{'testMemoryLimit', testMemoryLimit},
{'playerMemoryLimit', function()
local player = initPlayer()
testing.runLocalTest(player, 'playerMemoryLimit')
end},
{'player with equipped weapon on attack should damage health of other actors', function()
local player = initPlayer()
world.createObject('basic_dagger1h', 1):moveInto(player)
testing.runLocalTest(player, 'playerWeaponAttack')
end},
{'vfs', testVFS},
{'testCommitCrime', testCommitCrime},
{'recordModelProperty', testRecordModelProperty},
}
registerPlayerTest('playerYawRotation')
registerPlayerTest('playerPitchRotation')
registerPlayerTest('playerPitchAndYawRotation')
registerPlayerTest('playerRotation')
registerPlayerTest('playerForwardRunning')
registerPlayerTest('playerDiagonalWalking')
registerPlayerTest('findPath')
registerPlayerTest('findRandomPointAroundCircle')
registerPlayerTest('castNavigationRay')
registerPlayerTest('findNearestNavMeshPosition')
registerPlayerTest('playerMemoryLimit')
testing.registerGlobalTest('playerWeaponAttack', function()
local player = initPlayer()
world.createObject('basic_dagger1h', 1):moveInto(player)
testing.runLocalTest(player, 'playerWeaponAttack')
end)
return {
engineHandlers = {
onUpdate = testing.testRunner(tests),
onUpdate = testing.makeUpdateGlobal(),
},
eventHandlers = testing.eventHandlers,
}

View File

@ -2,12 +2,19 @@ local core = require('openmw.core')
local util = require('openmw.util')
local M = {}
local globalTestsOrder = {}
local globalTests = {}
local globalTestRunner = nil
local localTests = {}
local localTestRunner = nil
local currentLocalTest = nil
local currentLocalTestError = nil
function M.testRunner(tests)
function M.makeUpdateGlobal()
local fn = function()
for i, test in ipairs(tests) do
for i, test in ipairs(globalTestsOrder) do
local name, fn = unpack(test)
print('TEST_START', i, name)
local status, err = pcall(fn)
@ -27,6 +34,11 @@ function M.testRunner(tests)
end
end
function M.registerGlobalTest(name, fn)
globalTests[name] = fn
table.insert(globalTestsOrder, {name, fn})
end
function M.runLocalTest(obj, name)
currentLocalTest = name
currentLocalTestError = nil
@ -39,7 +51,21 @@ function M.runLocalTest(obj, name)
end
end
function M.expect(cond, delta, msg)
function M.registerLocalTest(name, fn)
localTests[name] = fn
end
function M.updateLocal()
if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then
if not core.isWorldPaused() then
coroutine.resume(localTestRunner)
end
else
localTestRunner = nil
end
end
function M.expect(cond, msg)
if not cond then
error(msg or '"true" expected', 2)
end
@ -182,28 +208,11 @@ function M.formatActualExpected(actual, expected)
return string.format('actual: %s, expected: %s', actual, expected)
end
local localTests = {}
local localTestRunner = nil
function M.registerLocalTest(name, fn)
localTests[name] = fn
end
function M.updateLocal()
if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then
if not core.isWorldPaused() then
coroutine.resume(localTestRunner)
end
else
localTestRunner = nil
end
end
M.eventHandlers = {
runLocalTest = function(name) -- used only in local scripts
fn = localTests[name]
if not fn then
core.sendGlobalEvent('localTestFinished', {name=name, errMsg='Test not found'})
core.sendGlobalEvent('localTestFinished', {name=name, errMsg='Local test is not found'})
return
end
localTestRunner = coroutine.create(function()

View File

@ -8,28 +8,13 @@ if not core.contentFiles.has('Morrowind.esm') then
error('This test requires Morrowind.esm')
end
function makeTests(modules)
local tests = {}
for _, moduleName in ipairs(modules) do
local module = require(moduleName)
for _, v in ipairs(module) do
table.insert(tests, {string.format('[%s] %s', moduleName, v[1]), v[2]})
end
end
return tests
end
local testModules = {
'global_issues',
'global_dialogues',
'global_mwscript',
}
require('global_issues')
require('global_dialogues')
require('global_mwscript')
return {
engineHandlers = {
onUpdate = testing.testRunner(makeTests(testModules)),
onUpdate = testing.makeUpdateGlobal(),
},
eventHandlers = testing.eventHandlers,
}

View File

@ -13,35 +13,37 @@ function iterateOverRecords(records)
return firstRecordId, lastRecordId, count
end
return {
{'Should support iteration over journal dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.journal.records)
testing.expectEqual(firstRecordId, '11111 test journal')
testing.expectEqual(lastRecordId, 'va_vamprich')
testing.expectEqual(count, 632)
end},
{'Should support iteration over topic dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.topic.records)
testing.expectEqual(firstRecordId, '1000-drake pledge')
testing.expectEqual(lastRecordId, 'zenithar')
testing.expectEqual(count, 1698)
end},
{'Should support iteration over greeting dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.greeting.records)
testing.expectEqual(firstRecordId, 'greeting 0')
testing.expectEqual(lastRecordId, 'greeting 9')
testing.expectEqual(count, 10)
end},
{'Should support iteration over persuasion dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.persuasion.records)
testing.expectEqual(firstRecordId, 'admire fail')
testing.expectEqual(lastRecordId, 'taunt success')
testing.expectEqual(count, 10)
end},
{'Should support iteration over voice dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.voice.records)
testing.expectEqual(firstRecordId, 'alarm')
testing.expectEqual(lastRecordId, 'thief')
testing.expectEqual(count, 8)
end},
}
testing.registerGlobalTest('[dialogues] Should support iteration over journal dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.journal.records)
testing.expectEqual(firstRecordId, '11111 test journal')
testing.expectEqual(lastRecordId, 'va_vamprich')
testing.expectEqual(count, 632)
end)
testing.registerGlobalTest('[dialogues] Should support iteration over topic dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.topic.records)
testing.expectEqual(firstRecordId, '1000-drake pledge')
testing.expectEqual(lastRecordId, 'zenithar')
testing.expectEqual(count, 1698)
end)
testing.registerGlobalTest('[dialogues] Should support iteration over greeting dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.greeting.records)
testing.expectEqual(firstRecordId, 'greeting 0')
testing.expectEqual(lastRecordId, 'greeting 9')
testing.expectEqual(count, 10)
end)
testing.registerGlobalTest('[dialogues] Should support iteration over persuasion dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.persuasion.records)
testing.expectEqual(firstRecordId, 'admire fail')
testing.expectEqual(lastRecordId, 'taunt success')
testing.expectEqual(count, 10)
end)
testing.registerGlobalTest('[dialogues] Should support iteration over voice dialogues', function()
local firstRecordId, lastRecordId, count = iterateOverRecords(core.dialogue.voice.records)
testing.expectEqual(firstRecordId, 'alarm')
testing.expectEqual(lastRecordId, 'thief')
testing.expectEqual(count, 8)
end)

View File

@ -4,37 +4,37 @@ local world = require('openmw.world')
local core = require('openmw.core')
local types = require('openmw.types')
return {
{'Player should be able to walk up stairs in Ebonheart docks (#4247)', function()
world.players[1]:teleport('', util.vector3(19867, -102180, -79), util.transform.rotateZ(math.rad(91)))
coroutine.yield()
testing.runLocalTest(world.players[1], 'Player should be able to walk up stairs in Ebonheart docks (#4247)')
end},
{'Guard in Imperial Prison Ship should find path (#7241)', function()
world.players[1]:teleport('Imperial Prison Ship', util.vector3(61, -135, -105), util.transform.rotateZ(math.rad(-20)))
coroutine.yield()
testing.runLocalTest(world.players[1], 'Guard in Imperial Prison Ship should find path (#7241)')
end},
{'Should keep reference to an object moved into container (#7663)', function()
world.players[1]:teleport('ToddTest', util.vector3(2176, 3648, -191), util.transform.rotateZ(math.rad(0)))
coroutine.yield()
local barrel = world.createObject('barrel_01', 1)
local fargothRing = world.createObject('ring_keley', 1)
coroutine.yield()
testing.expectEqual(types.Container.inventory(barrel):find('ring_keley'), nil)
fargothRing:moveInto(types.Container.inventory(barrel))
coroutine.yield()
testing.expectEqual(fargothRing.recordId, 'ring_keley')
local isFargothRing = function(actual)
if actual == nil then
return 'ring_keley is not found'
end
if actual.id ~= fargothRing.id then
return 'found ring_keley id does not match expected: actual=' .. tostring(actual.id)
.. ', expected=' .. tostring(fargothRing.id)
end
return ''
testing.registerGlobalTest('[issues] Player should be able to walk up stairs in Ebonheart docks (#4247)', function()
world.players[1]:teleport('', util.vector3(19867, -102180, -79), util.transform.rotateZ(math.rad(91)))
coroutine.yield()
testing.runLocalTest(world.players[1], 'Player should be able to walk up stairs in Ebonheart docks (#4247)')
end)
testing.registerGlobalTest('[issues] Guard in Imperial Prison Ship should find path (#7241)', function()
world.players[1]:teleport('Imperial Prison Ship', util.vector3(61, -135, -105), util.transform.rotateZ(math.rad(-20)))
coroutine.yield()
testing.runLocalTest(world.players[1], 'Guard in Imperial Prison Ship should find path (#7241)')
end)
testing.registerGlobalTest('[issues] Should keep reference to an object moved into container (#7663)', function()
world.players[1]:teleport('ToddTest', util.vector3(2176, 3648, -191), util.transform.rotateZ(math.rad(0)))
coroutine.yield()
local barrel = world.createObject('barrel_01', 1)
local fargothRing = world.createObject('ring_keley', 1)
coroutine.yield()
testing.expectEqual(types.Container.inventory(barrel):find('ring_keley'), nil)
fargothRing:moveInto(types.Container.inventory(barrel))
coroutine.yield()
testing.expectEqual(fargothRing.recordId, 'ring_keley')
local isFargothRing = function(actual)
if actual == nil then
return 'ring_keley is not found'
end
testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing)
end},
}
if actual.id ~= fargothRing.id then
return 'found ring_keley id does not match expected: actual=' .. tostring(actual.id)
.. ', expected=' .. tostring(fargothRing.id)
end
return ''
end
testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing)
end)

View File

@ -14,38 +14,38 @@ function iterateOverVariables(variables)
return first, last, count
end
return {
{'Should support iteration over an empty set of script variables', function()
local mainVars = world.mwscript.getGlobalScript('main').variables
local first, last, count = iterateOverVariables(mainVars)
testing.expectEqual(first, nil)
testing.expectEqual(last, nil)
testing.expectEqual(count, 0)
testing.expectEqual(count, #mainVars)
end},
{'Should support iteration of script variables', function()
local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867))
local jiubVars = world.mwscript.getLocalScript(jiub).variables
local first, last, count = iterateOverVariables(jiubVars)
testing.registerGlobalTest('[mwscript] Should support iteration over an empty set of script variables', function()
local mainVars = world.mwscript.getGlobalScript('main').variables
local first, last, count = iterateOverVariables(mainVars)
testing.expectEqual(first, nil)
testing.expectEqual(last, nil)
testing.expectEqual(count, 0)
testing.expectEqual(count, #mainVars)
end)
testing.expectEqual(first, 'state')
testing.expectEqual(last, 'timer')
testing.expectEqual(count, 3)
testing.expectEqual(count, #jiubVars)
end},
{'Should support numeric and string indices for getting and setting', function()
local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867))
local jiubVars = world.mwscript.getLocalScript(jiub).variables
testing.registerGlobalTest('[mwscript] Should support iteration of script variables', function()
local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867))
local jiubVars = world.mwscript.getLocalScript(jiub).variables
local first, last, count = iterateOverVariables(jiubVars)
testing.expectEqual(jiubVars[1], jiubVars.state)
testing.expectEqual(jiubVars[2], jiubVars.wandering)
testing.expectEqual(jiubVars[3], jiubVars.timer)
testing.expectEqual(first, 'state')
testing.expectEqual(last, 'timer')
testing.expectEqual(count, 3)
testing.expectEqual(count, #jiubVars)
end)
jiubVars[1] = 123;
testing.expectEqual(jiubVars.state, 123)
jiubVars.wandering = 42;
testing.expectEqual(jiubVars[2], 42)
jiubVars[3] = 1.25;
testing.expectEqual(jiubVars.timer, 1.25)
end},
}
testing.registerGlobalTest('[mwscript] Should support numeric and string indices for getting and setting', function()
local jiub = world.getObjectByFormId(core.getFormId('Morrowind.esm', 172867))
local jiubVars = world.mwscript.getLocalScript(jiub).variables
testing.expectEqual(jiubVars[1], jiubVars.state)
testing.expectEqual(jiubVars[2], jiubVars.wandering)
testing.expectEqual(jiubVars[3], jiubVars.timer)
jiubVars[1] = 123;
testing.expectEqual(jiubVars.state, 123)
jiubVars.wandering = 42;
testing.expectEqual(jiubVars[2], 42)
jiubVars[3] = 1.25;
testing.expectEqual(jiubVars.timer, 1.25)
end)