mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-14 01:19:59 +00:00
Merge branch 'lua_test_menu' into 'master'
Run Lua integration tests starting with menu script (#8370) Closes #8370 See merge request OpenMW/openmw!4556
This commit is contained in:
commit
a488aed600
@ -9,7 +9,7 @@ git checkout FETCH_HEAD
|
||||
cd ..
|
||||
|
||||
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \
|
||||
scripts/integration_tests.py --omw build/install/bin/openmw --workdir integration_tests_output example-suite/
|
||||
scripts/integration_tests.py --verbose --omw build/install/bin/openmw --workdir integration_tests_output example-suite/
|
||||
|
||||
ls integration_tests_output/*.osg_stats.log | while read v; do
|
||||
scripts/osg_stats.py --stats '.*' --regexp_match < "${v}"
|
||||
|
@ -7,7 +7,7 @@ local vfs = require('openmw.vfs')
|
||||
local world = require('openmw.world')
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
local function testTimers()
|
||||
testing.registerGlobalTest('timers', function()
|
||||
testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result')
|
||||
testing.expectAlmostEqual(core.getSimulationTimeScale(), 1, 'incorrect getSimulationTimeScale result')
|
||||
|
||||
@ -39,9 +39,10 @@ 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('teleport', function()
|
||||
local player = world.players[1]
|
||||
player:teleport('', util.vector3(100, 50, 500), util.transform.rotateZ(math.rad(90)))
|
||||
coroutine.yield()
|
||||
testing.expect(player.cell.isExterior, 'teleport to exterior failed')
|
||||
@ -71,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('getGMST', 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('MWScript', function()
|
||||
local variableStoreCount = 18
|
||||
local variableStore = world.mwscript.getGlobalVariables(player)
|
||||
testing.expectEqual(variableStoreCount, #variableStore)
|
||||
@ -100,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)
|
||||
@ -121,7 +122,7 @@ local function testRecordStore(store, storeName, skipPairs)
|
||||
testing.expectEqual(status, true, storeName)
|
||||
end
|
||||
|
||||
local function testRecordStores()
|
||||
testing.registerGlobalTest('record stores', function()
|
||||
for key, type in pairs(types) do
|
||||
if type.records then
|
||||
testRecordStore(type, key)
|
||||
@ -140,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('record creation', function()
|
||||
local newLight = {
|
||||
isCarriable = true,
|
||||
isDynamic = true,
|
||||
@ -165,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('UTF-8 characters', function()
|
||||
testing.expectEqual(utf8.codepoint("😀"), 0x1F600)
|
||||
|
||||
local chars = {}
|
||||
@ -192,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('UTF-8 strings', function()
|
||||
local utf8str = "Hello, 你好, 🌎!"
|
||||
|
||||
local str = ""
|
||||
@ -205,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('memory limit', function()
|
||||
local ok, err = pcall(function()
|
||||
local t = {}
|
||||
local n = 1
|
||||
@ -218,14 +219,16 @@ 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]
|
||||
player:teleport('', util.vector3(4096, 4096, 1745), util.transform.identity)
|
||||
coroutine.yield()
|
||||
return player
|
||||
end
|
||||
|
||||
local function testVFS()
|
||||
testing.registerGlobalTest('vfs', function()
|
||||
local file = 'test_vfs_dir/lines.txt'
|
||||
local nosuchfile = 'test_vfs_dir/nosuchfile'
|
||||
testing.expectEqual(vfs.fileExists(file), true, 'lines.txt should exist')
|
||||
@ -269,12 +272,11 @@ local function testVFS()
|
||||
for _,v in pairs(expectedLines) do
|
||||
testing.expectEqual(getLine(), v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function testCommitCrime()
|
||||
initPlayer()
|
||||
local player = world.players[1]
|
||||
testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `testCommitCrime`')
|
||||
testing.registerGlobalTest('commit crime', function()
|
||||
local player = initPlayer()
|
||||
testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `commit crime`')
|
||||
testing.expectEqual(I.Crimes == nil, false, 'Crimes interface should be available in global contexts')
|
||||
|
||||
-- Reset crime level to have a clean slate
|
||||
@ -292,82 +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()
|
||||
initPlayer()
|
||||
testing.registerGlobalTest('record model property', function()
|
||||
local player = world.players[1]
|
||||
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()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'playerYawRotation')
|
||||
end},
|
||||
{'rotating player with controls.pitchChange should change rotation', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'playerPitchRotation')
|
||||
end},
|
||||
{'rotating player with controls.pitchChange and controls.yawChange should change rotation', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'playerPitchAndYawRotation')
|
||||
end},
|
||||
{'rotating player should not lead to nan rotation', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'playerRotation')
|
||||
end},
|
||||
{'playerForwardRunning', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'playerForwardRunning')
|
||||
end},
|
||||
{'playerDiagonalWalking', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'playerDiagonalWalking')
|
||||
end},
|
||||
{'findPath', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'findPath')
|
||||
end},
|
||||
{'findRandomPointAroundCircle', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'findRandomPointAroundCircle')
|
||||
end},
|
||||
{'castNavigationRay', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'castNavigationRay')
|
||||
end},
|
||||
{'findNearestNavMeshPosition', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'findNearestNavMeshPosition')
|
||||
end},
|
||||
{'teleport', testTeleport},
|
||||
{'getGMST', testGetGMST},
|
||||
{'recordStores', testRecordStores},
|
||||
{'recordCreation', testRecordCreation},
|
||||
{'utf8Chars', testUTF8Chars},
|
||||
{'utf8Strings', testUTF8Strings},
|
||||
{'mwscript', testMWScript},
|
||||
{'testMemoryLimit', testMemoryLimit},
|
||||
{'playerMemoryLimit', function()
|
||||
initPlayer()
|
||||
testing.runLocalTest(player, 'playerMemoryLimit')
|
||||
end},
|
||||
{'player with equipped weapon on attack should damage health of other actors', function()
|
||||
initPlayer()
|
||||
world.createObject('basic_dagger1h', 1):moveInto(player)
|
||||
testing.runLocalTest(player, 'playerWeaponAttack')
|
||||
end},
|
||||
{'vfs', testVFS},
|
||||
{'testCommitCrime', testCommitCrime},
|
||||
{'recordModelProperty', testRecordModelProperty},
|
||||
}
|
||||
registerPlayerTest('player yaw rotation')
|
||||
registerPlayerTest('player pitch rotation')
|
||||
registerPlayerTest('player pitch and yaw rotation')
|
||||
registerPlayerTest('player rotation')
|
||||
registerPlayerTest('player forward running')
|
||||
registerPlayerTest('player diagonal walking')
|
||||
registerPlayerTest('findPath')
|
||||
registerPlayerTest('findRandomPointAroundCircle')
|
||||
registerPlayerTest('castNavigationRay')
|
||||
registerPlayerTest('findNearestNavMeshPosition')
|
||||
registerPlayerTest('player memory limit')
|
||||
|
||||
testing.registerGlobalTest('player weapon attack', function()
|
||||
local player = initPlayer()
|
||||
world.createObject('basic_dagger1h', 1):moveInto(player)
|
||||
testing.runLocalTest(player, 'player weapon attack')
|
||||
end)
|
||||
|
||||
return {
|
||||
engineHandlers = {
|
||||
onUpdate = testing.testRunner(tests),
|
||||
onPlayerAdded = function(p) player = p end,
|
||||
onUpdate = testing.updateGlobal,
|
||||
},
|
||||
eventHandlers = testing.eventHandlers,
|
||||
eventHandlers = testing.globalEventHandlers,
|
||||
}
|
43
scripts/data/integration_tests/test_lua_api/menu.lua
Normal file
43
scripts/data/integration_tests/test_lua_api/menu.lua
Normal file
@ -0,0 +1,43 @@
|
||||
local testing = require('testing_util')
|
||||
local menu = require('openmw.menu')
|
||||
|
||||
local function registerGlobalTest(name, description)
|
||||
testing.registerMenuTest(description or name, function()
|
||||
menu.newGame()
|
||||
coroutine.yield()
|
||||
testing.runGlobalTest(name)
|
||||
end)
|
||||
end
|
||||
|
||||
registerGlobalTest('timers')
|
||||
registerGlobalTest('teleport')
|
||||
registerGlobalTest('getGMST')
|
||||
registerGlobalTest('MWScript')
|
||||
registerGlobalTest('record stores')
|
||||
registerGlobalTest('record creation')
|
||||
registerGlobalTest('UTF-8 characters')
|
||||
registerGlobalTest('UTF-8 strings')
|
||||
registerGlobalTest('memory limit')
|
||||
registerGlobalTest('vfs')
|
||||
registerGlobalTest('commit crime')
|
||||
registerGlobalTest('record model property')
|
||||
|
||||
registerGlobalTest('player yaw rotation', 'rotating player with controls.yawChange should change rotation')
|
||||
registerGlobalTest('player pitch rotation', 'rotating player with controls.pitchChange should change rotation')
|
||||
registerGlobalTest('player pitch and yaw rotation', 'rotating player with controls.pitchChange and controls.yawChange should change rotation')
|
||||
registerGlobalTest('player rotation', 'rotating player should not lead to nan rotation')
|
||||
registerGlobalTest('player forward running')
|
||||
registerGlobalTest('player diagonal walking')
|
||||
registerGlobalTest('findPath')
|
||||
registerGlobalTest('findRandomPointAroundCircle')
|
||||
registerGlobalTest('castNavigationRay')
|
||||
registerGlobalTest('findNearestNavMeshPosition')
|
||||
registerGlobalTest('player memory limit')
|
||||
registerGlobalTest('player weapon attack', 'player with equipped weapon on attack should damage health of other actors')
|
||||
|
||||
return {
|
||||
engineHandlers = {
|
||||
onFrame = testing.makeUpdateMenu(),
|
||||
},
|
||||
eventHandlers = testing.menuEventHandlers,
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
content=test.omwscripts
|
||||
content=test_lua_api.omwscripts
|
||||
|
||||
# Needed to test `core.getGMST`
|
||||
fallback=Water_RippleFrameCount,4
|
||||
|
@ -40,7 +40,7 @@ local function rotateByPitch(object, target)
|
||||
rotate(object, target, nil)
|
||||
end
|
||||
|
||||
testing.registerLocalTest('playerYawRotation',
|
||||
testing.registerLocalTest('player yaw rotation',
|
||||
function()
|
||||
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
|
||||
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
|
||||
@ -60,7 +60,7 @@ testing.registerLocalTest('playerYawRotation',
|
||||
testing.expectEqualWithDelta(gamma2, initialGammaZYX, 0.05, 'Gamma rotation in ZYX convention should not change')
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('playerPitchRotation',
|
||||
testing.registerLocalTest('player pitch rotation',
|
||||
function()
|
||||
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
|
||||
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
|
||||
@ -80,7 +80,7 @@ testing.registerLocalTest('playerPitchRotation',
|
||||
testing.expectEqualWithDelta(gamma2, targetPitch, 0.05, 'Incorrect gamma rotation in ZYX convention')
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('playerPitchAndYawRotation',
|
||||
testing.registerLocalTest('player pitch and yaw rotation',
|
||||
function()
|
||||
local targetPitch = math.rad(-30)
|
||||
local targetYaw = math.rad(-60)
|
||||
@ -99,7 +99,7 @@ testing.registerLocalTest('playerPitchAndYawRotation',
|
||||
testing.expectEqualWithDelta(gamma2, math.rad(-16), 0.05, 'Incorrect gamma rotation in ZYX convention')
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('playerRotation',
|
||||
testing.registerLocalTest('player rotation',
|
||||
function()
|
||||
local rotation = math.sqrt(2)
|
||||
local endTime = core.getSimulationTime() + 3
|
||||
@ -123,7 +123,7 @@ testing.registerLocalTest('playerRotation',
|
||||
end
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('playerForwardRunning',
|
||||
testing.registerLocalTest('player forward running',
|
||||
function()
|
||||
local startPos = self.position
|
||||
local endTime = core.getSimulationTime() + 1
|
||||
@ -141,7 +141,7 @@ testing.registerLocalTest('playerForwardRunning',
|
||||
testing.expectEqualWithDelta(direction.y, 1, 0.1, 'Run forward, Y coord')
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('playerDiagonalWalking',
|
||||
testing.registerLocalTest('player diagonal walking',
|
||||
function()
|
||||
local startPos = self.position
|
||||
local endTime = core.getSimulationTime() + 1
|
||||
@ -220,7 +220,7 @@ testing.registerLocalTest('findNearestNavMeshPosition',
|
||||
'Navigation mesh position ' .. testing.formatActualExpected(result, expected))
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('playerMemoryLimit',
|
||||
testing.registerLocalTest('player memory limit',
|
||||
function()
|
||||
local ok, err = pcall(function()
|
||||
local str = 'a'
|
||||
@ -232,7 +232,7 @@ testing.registerLocalTest('playerMemoryLimit',
|
||||
testing.expectEqual(err, 'not enough memory')
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('playerWeaponAttack',
|
||||
testing.registerLocalTest('player weapon attack',
|
||||
function()
|
||||
camera.setMode(camera.MODE.ThirdPerson)
|
||||
|
||||
@ -346,5 +346,5 @@ return {
|
||||
engineHandlers = {
|
||||
onFrame = testing.updateLocal,
|
||||
},
|
||||
eventHandlers = testing.eventHandlers
|
||||
eventHandlers = testing.localEventHandlers,
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
GLOBAL: test.lua
|
||||
PLAYER: player.lua
|
@ -0,0 +1,3 @@
|
||||
MENU: menu.lua
|
||||
GLOBAL: global.lua
|
||||
PLAYER: player.lua
|
@ -2,23 +2,22 @@ local core = require('openmw.core')
|
||||
local util = require('openmw.util')
|
||||
|
||||
local M = {}
|
||||
|
||||
local menuTestsOrder = {}
|
||||
local menuTests = {}
|
||||
|
||||
local globalTestsOrder = {}
|
||||
local globalTests = {}
|
||||
local globalTestRunner = nil
|
||||
local currentGlobalTest = nil
|
||||
local currentGlobalTestError = nil
|
||||
|
||||
local localTests = {}
|
||||
local localTestRunner = nil
|
||||
local currentLocalTest = nil
|
||||
local currentLocalTestError = nil
|
||||
|
||||
function M.testRunner(tests)
|
||||
local fn = function()
|
||||
for i, test in ipairs(tests) do
|
||||
local name, fn = unpack(test)
|
||||
print('TEST_START', i, name)
|
||||
local status, err = pcall(fn)
|
||||
if status then
|
||||
print('TEST_OK', i, name)
|
||||
else
|
||||
print('TEST_FAILED', i, name, err)
|
||||
end
|
||||
end
|
||||
core.quit()
|
||||
end
|
||||
local function makeTestCoroutine(fn)
|
||||
local co = coroutine.create(fn)
|
||||
return function()
|
||||
if coroutine.status(co) ~= 'dead' then
|
||||
@ -27,6 +26,64 @@ function M.testRunner(tests)
|
||||
end
|
||||
end
|
||||
|
||||
local function runTests(tests)
|
||||
for i, test in ipairs(tests) do
|
||||
local name, fn = unpack(test)
|
||||
print('TEST_START', i, name)
|
||||
local status, err = pcall(fn)
|
||||
if status then
|
||||
print('TEST_OK', i, name)
|
||||
else
|
||||
print('TEST_FAILED', i, name, err)
|
||||
end
|
||||
end
|
||||
core.quit()
|
||||
end
|
||||
|
||||
function M.makeUpdateMenu()
|
||||
return makeTestCoroutine(function()
|
||||
print('Running menu tests...')
|
||||
runTests(menuTestsOrder)
|
||||
end)
|
||||
end
|
||||
|
||||
function M.makeUpdateGlobal()
|
||||
return makeTestCoroutine(function()
|
||||
print('Running global tests...')
|
||||
runTests(globalTestsOrder)
|
||||
end)
|
||||
end
|
||||
|
||||
function M.registerMenuTest(name, fn)
|
||||
menuTests[name] = fn
|
||||
table.insert(menuTestsOrder, {name, fn})
|
||||
end
|
||||
|
||||
function M.runGlobalTest(name)
|
||||
currentGlobalTest = name
|
||||
currentGlobalTestError = nil
|
||||
core.sendGlobalEvent('runGlobalTest', name)
|
||||
while currentGlobalTest do
|
||||
coroutine.yield()
|
||||
end
|
||||
if currentGlobalTestError then
|
||||
error(currentGlobalTestError, 2)
|
||||
end
|
||||
end
|
||||
|
||||
function M.registerGlobalTest(name, fn)
|
||||
globalTests[name] = fn
|
||||
table.insert(globalTestsOrder, {name, fn})
|
||||
end
|
||||
|
||||
function M.updateGlobal()
|
||||
if globalTestRunner and coroutine.status(globalTestRunner) ~= 'dead' then
|
||||
coroutine.resume(globalTestRunner)
|
||||
else
|
||||
globalTestRunner = nil
|
||||
end
|
||||
end
|
||||
|
||||
function M.runLocalTest(obj, name)
|
||||
currentLocalTest = name
|
||||
currentLocalTestError = nil
|
||||
@ -39,7 +96,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 +253,50 @@ 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)
|
||||
-- used only in menu scripts
|
||||
M.menuEventHandlers = {
|
||||
globalTestFinished = function(data)
|
||||
if data.name ~= currentGlobalTest then
|
||||
error(string.format('globalTestFinished with incorrect name %s, expected %s', data.name, currentGlobalTest), 2)
|
||||
end
|
||||
else
|
||||
localTestRunner = nil
|
||||
end
|
||||
end
|
||||
currentGlobalTest = nil
|
||||
currentGlobalTestError = data.errMsg
|
||||
end,
|
||||
}
|
||||
|
||||
M.eventHandlers = {
|
||||
runLocalTest = function(name) -- used only in local scripts
|
||||
-- used only in global scripts
|
||||
M.globalEventHandlers = {
|
||||
runGlobalTest = function(name)
|
||||
fn = globalTests[name]
|
||||
local types = require('openmw.types')
|
||||
local world = require('openmw.world')
|
||||
if not fn then
|
||||
types.Player.sendMenuEvent(world.players[1], 'globalTestFinished', {name=name, errMsg='Global test is not found'})
|
||||
return
|
||||
end
|
||||
globalTestRunner = coroutine.create(function()
|
||||
local status, err = pcall(fn)
|
||||
if status then
|
||||
err = nil
|
||||
end
|
||||
types.Player.sendMenuEvent(world.players[1], 'globalTestFinished', {name=name, errMsg=err})
|
||||
end)
|
||||
end,
|
||||
localTestFinished = function(data)
|
||||
if data.name ~= currentLocalTest then
|
||||
error(string.format('localTestFinished with incorrect name %s, expected %s', data.name, currentLocalTest), 2)
|
||||
end
|
||||
currentLocalTest = nil
|
||||
currentLocalTestError = data.errMsg
|
||||
end,
|
||||
}
|
||||
|
||||
-- used only in local scripts
|
||||
M.localEventHandlers = {
|
||||
runLocalTest = function(name)
|
||||
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()
|
||||
@ -214,13 +307,6 @@ M.eventHandlers = {
|
||||
core.sendGlobalEvent('localTestFinished', {name=name, errMsg=err})
|
||||
end)
|
||||
end,
|
||||
localTestFinished = function(data) -- used only in global scripts
|
||||
if data.name ~= currentLocalTest then
|
||||
error(string.format('localTestFinished with incorrect name %s, expected %s', data.name, currentLocalTest))
|
||||
end
|
||||
currentLocalTest = nil
|
||||
currentLocalTestError = data.errMsg
|
||||
end,
|
||||
}
|
||||
|
||||
return M
|
||||
|
@ -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,
|
||||
eventHandlers = testing.globalEventHandlers,
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -80,5 +80,5 @@ return {
|
||||
engineHandlers = {
|
||||
onFrame = testing.updateLocal,
|
||||
},
|
||||
eventHandlers = testing.eventHandlers
|
||||
eventHandlers = testing.localEventHandlers,
|
||||
}
|
||||
|
@ -1,6 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse, datetime, os, subprocess, sys, shutil
|
||||
import argparse
|
||||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
parser = argparse.ArgumentParser(description="OpenMW integration tests.")
|
||||
@ -40,12 +47,13 @@ testing_util_dir = tests_dir / "testing_util"
|
||||
time_str = datetime.datetime.now().strftime("%Y-%m-%d-%H.%M.%S")
|
||||
|
||||
|
||||
def runTest(name):
|
||||
print(f"Start {name}")
|
||||
def run_test(test_name):
|
||||
start = time.time()
|
||||
print(f'[----------] Running tests from {test_name}')
|
||||
shutil.rmtree(config_dir, ignore_errors=True)
|
||||
config_dir.mkdir()
|
||||
shutil.copyfile(example_suite_dir / "settings.cfg", config_dir / "settings.cfg")
|
||||
test_dir = tests_dir / name
|
||||
test_dir = tests_dir / test_name
|
||||
with open(config_dir / "openmw.cfg", "w", encoding="utf-8") as omw_cfg:
|
||||
for path in content_paths:
|
||||
omw_cfg.write(f'data="{path.parent}"\n')
|
||||
@ -58,10 +66,8 @@ def runTest(name):
|
||||
)
|
||||
for path in content_paths:
|
||||
omw_cfg.write(f'content={path.name}\n')
|
||||
if (test_dir / "openmw.cfg").exists():
|
||||
omw_cfg.write(open(test_dir / "openmw.cfg").read())
|
||||
elif (test_dir / "test.omwscripts").exists():
|
||||
omw_cfg.write("content=test.omwscripts\n")
|
||||
with open(test_dir / "openmw.cfg") as stream:
|
||||
omw_cfg.write(stream.read())
|
||||
with open(config_dir / "settings.cfg", "a", encoding="utf-8") as settings_cfg:
|
||||
settings_cfg.write(
|
||||
"[Video]\n"
|
||||
@ -74,61 +80,76 @@ def runTest(name):
|
||||
f"memory limit = {1024 * 1024 * 256}\n"
|
||||
)
|
||||
stdout_lines = list()
|
||||
exit_ok = True
|
||||
test_success = True
|
||||
fatal_errors = list()
|
||||
with subprocess.Popen(
|
||||
[openmw_binary, "--replace=config", "--config", config_dir, "--skip-menu", "--no-grab", "--no-sound"],
|
||||
[openmw_binary, "--replace=config", "--config", config_dir, "--no-grab"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
encoding="utf-8",
|
||||
env={
|
||||
"OPENMW_OSG_STATS_FILE": str(work_dir / f"{name}.{time_str}.osg_stats.log"),
|
||||
"OPENMW_OSG_STATS_FILE": str(work_dir / f"{test_name}.{time_str}.osg_stats.log"),
|
||||
"OPENMW_OSG_STATS_LIST": "times",
|
||||
**os.environ,
|
||||
},
|
||||
) as process:
|
||||
quit_requested = False
|
||||
running_test_number = None
|
||||
running_test_name = None
|
||||
count = 0
|
||||
failed_tests = list()
|
||||
test_start = None
|
||||
for line in process.stdout:
|
||||
if args.verbose:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
stdout_lines.append(line)
|
||||
words = line.split(" ")
|
||||
if len(words) > 1 and words[1] == "E]":
|
||||
print(line, end="")
|
||||
elif "Quit requested by a Lua script" in line:
|
||||
if "Quit requested by a Lua script" in line:
|
||||
quit_requested = True
|
||||
elif "TEST_START" in line:
|
||||
w = line.split("TEST_START")[1].split("\t")
|
||||
print(f"TEST {w[2].strip()}\t\t", end="")
|
||||
test_start = time.time()
|
||||
number, name = line.split("TEST_START")[1].strip().split("\t", maxsplit=1)
|
||||
running_test_number = int(number)
|
||||
running_test_name = name
|
||||
count += 1
|
||||
print(f"[ RUN ] {running_test_name}")
|
||||
elif "TEST_OK" in line:
|
||||
print(f"OK")
|
||||
duration = (time.time() - test_start) * 1000
|
||||
number, name = line.split("TEST_OK")[1].strip().split("\t", maxsplit=1)
|
||||
assert running_test_number == int(number)
|
||||
print(f"[ OK ] {running_test_name} ({duration:.3f} ms)")
|
||||
elif "TEST_FAILED" in line:
|
||||
w = line.split("TEST_FAILED")[1].split("\t")
|
||||
print(f"FAILED {w[3]}\t\t")
|
||||
test_success = False
|
||||
duration = (time.time() - test_start) * 1000
|
||||
number, name, error = line.split("TEST_FAILED")[1].strip().split("\t", maxsplit=2)
|
||||
assert running_test_number == int(number)
|
||||
print(error)
|
||||
print(f"[ FAILED ] {running_test_name} ({duration:.3f} ms)")
|
||||
failed_tests.append(running_test_name)
|
||||
process.wait(5)
|
||||
if not quit_requested:
|
||||
print("ERROR: Unexpected termination")
|
||||
exit_ok = False
|
||||
fatal_errors.append("unexpected termination")
|
||||
if process.returncode != 0:
|
||||
print(f"ERROR: openmw exited with code {process.returncode}")
|
||||
exit_ok = False
|
||||
fatal_errors.append(f"openmw exited with code {process.returncode}")
|
||||
if os.path.exists(config_dir / "openmw.log"):
|
||||
shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log")
|
||||
if not exit_ok and not args.verbose:
|
||||
shutil.copyfile(config_dir / "openmw.log", work_dir / f"{test_name}.{time_str}.log")
|
||||
if fatal_errors and not args.verbose:
|
||||
sys.stdout.writelines(stdout_lines)
|
||||
if test_success and exit_ok:
|
||||
print(f"{name} succeeded")
|
||||
else:
|
||||
print(f"{name} failed")
|
||||
return test_success and exit_ok
|
||||
total_duration = (time.time() - start) * 1000
|
||||
print(f'\n[----------] {count} tests from {test_name} ({total_duration:.3f} ms total)')
|
||||
print(f"[ PASSED ] {count - len(failed_tests)} tests.")
|
||||
if fatal_errors:
|
||||
print(f"[ FAILED ] fatal error: {'; '.join(fatal_errors)}")
|
||||
if failed_tests:
|
||||
print(f"[ FAILED ] {len(failed_tests)} tests, listed below:")
|
||||
for failed_test in failed_tests:
|
||||
print(f"[ FAILED ] {failed_test}")
|
||||
return len(failed_tests) == 0 and not fatal_errors
|
||||
|
||||
|
||||
status = 0
|
||||
for entry in tests_dir.glob("test_*"):
|
||||
if entry.is_dir():
|
||||
if not runTest(entry.name):
|
||||
if not run_test(entry.name):
|
||||
status = -1
|
||||
if status == 0:
|
||||
shutil.rmtree(config_dir, ignore_errors=True)
|
||||
|
Loading…
x
Reference in New Issue
Block a user