mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-04-28 12:58:00 +03:00

This allows writing tests for menu scripts. Keep global script as entry point to morrowind tests. Fix menu.newGame and menu.loadGame to hide main menu.
334 lines
13 KiB
Lua
334 lines
13 KiB
Lua
local testing = require('testing_util')
|
|
local core = require('openmw.core')
|
|
local async = require('openmw.async')
|
|
local util = require('openmw.util')
|
|
local types = require('openmw.types')
|
|
local vfs = require('openmw.vfs')
|
|
local world = require('openmw.world')
|
|
local I = require('openmw.interfaces')
|
|
|
|
testing.registerGlobalTest('timers', function()
|
|
testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result')
|
|
testing.expectAlmostEqual(core.getSimulationTimeScale(), 1, 'incorrect getSimulationTimeScale result')
|
|
|
|
local startGameTime = core.getGameTime()
|
|
local startSimulationTime = core.getSimulationTime()
|
|
|
|
local ts1, ts2, th1, th2
|
|
local cb = async:registerTimerCallback("tfunc", function(arg)
|
|
if arg == 'g' then
|
|
th1 = core.getGameTime() - startGameTime
|
|
else
|
|
ts1 = core.getSimulationTime() - startSimulationTime
|
|
end
|
|
end)
|
|
async:newGameTimer(36, cb, 'g')
|
|
async:newSimulationTimer(0.5, cb, 's')
|
|
async:newUnsavableGameTimer(72, function()
|
|
th2 = core.getGameTime() - startGameTime
|
|
end)
|
|
async:newUnsavableSimulationTimer(1, function()
|
|
ts2 = core.getSimulationTime() - startSimulationTime
|
|
end)
|
|
|
|
while not (ts1 and ts2 and th1 and th2) do
|
|
coroutine.yield()
|
|
end
|
|
|
|
testing.expectGreaterOrEqual(th1, 36, 'async:newGameTimer failed')
|
|
testing.expectGreaterOrEqual(ts1, 0.5, 'async:newSimulationTimer failed')
|
|
testing.expectGreaterOrEqual(th2, 72, 'async:newUnsavableGameTimer failed')
|
|
testing.expectGreaterOrEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed')
|
|
end)
|
|
|
|
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')
|
|
testing.expectEqualWithDelta(player.position.x, 100, 1, 'incorrect position after teleporting')
|
|
testing.expectEqualWithDelta(player.position.y, 50, 1, 'incorrect position after teleporting')
|
|
testing.expectEqualWithDelta(player.position.z, 500, 1, 'incorrect position after teleporting')
|
|
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(90), 0.05, 'incorrect yaw rotation after teleporting')
|
|
testing.expectEqualWithDelta(player.rotation:getPitch(), math.rad(0), 0.05, 'incorrect pitch rotation after teleporting')
|
|
|
|
local rotationX1, rotationZ1 = player.rotation:getAnglesXZ()
|
|
testing.expectEqualWithDelta(rotationX1, math.rad(0), 0.05, 'incorrect x rotation from getAnglesXZ after teleporting')
|
|
testing.expectEqualWithDelta(rotationZ1, math.rad(90), 0.05, 'incorrect z rotation from getAnglesXZ after teleporting')
|
|
|
|
local rotationZ2, rotationY2, rotationX2 = player.rotation:getAnglesZYX()
|
|
testing.expectEqualWithDelta(rotationZ2, math.rad(90), 0.05, 'incorrect z rotation from getAnglesZYX after teleporting')
|
|
testing.expectEqualWithDelta(rotationY2, math.rad(0), 0.05, 'incorrect y rotation from getAnglesZYX after teleporting')
|
|
testing.expectEqualWithDelta(rotationX2, math.rad(0), 0.05, 'incorrect x rotation from getAnglesZYX after teleporting')
|
|
|
|
player:teleport('', player.position, {rotation=util.transform.rotateZ(math.rad(-90)), onGround=true})
|
|
coroutine.yield()
|
|
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(-90), 0.05, 'options.rotation is not working')
|
|
testing.expectLessOrEqual(player.position.z, 400, 'options.onGround is not working')
|
|
|
|
player:teleport('', util.vector3(50, -100, 0))
|
|
coroutine.yield()
|
|
testing.expect(player.cell.isExterior, 'teleport to exterior failed')
|
|
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)
|
|
|
|
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)
|
|
|
|
testing.registerGlobalTest('MWScript', function()
|
|
local variableStoreCount = 18
|
|
local variableStore = world.mwscript.getGlobalVariables(player)
|
|
testing.expectEqual(variableStoreCount, #variableStore)
|
|
|
|
variableStore.year = 5
|
|
testing.expectEqual(5, variableStore.year)
|
|
variableStore.year = 1
|
|
local indexCheck = 0
|
|
for index, value in ipairs(variableStore) do
|
|
testing.expectEqual(variableStore[index], value)
|
|
indexCheck = indexCheck + 1
|
|
end
|
|
testing.expectEqual(variableStoreCount, indexCheck)
|
|
indexCheck = 0
|
|
for index, value in pairs(variableStore) do
|
|
testing.expectEqual(variableStore[index], value)
|
|
indexCheck = indexCheck + 1
|
|
end
|
|
testing.expectEqual(variableStoreCount, indexCheck)
|
|
end)
|
|
|
|
local function testRecordStore(store, storeName, skipPairs)
|
|
testing.expect(store.records)
|
|
local firstRecord = store.records[1]
|
|
if not firstRecord then
|
|
return
|
|
end
|
|
testing.expectEqual(firstRecord.id, store.records[firstRecord.id].id)
|
|
local status, _ = pcall(function()
|
|
for index, value in ipairs(store.records) do
|
|
if value.id == firstRecord.id then
|
|
testing.expectEqual(index, 1, storeName)
|
|
break
|
|
end
|
|
end
|
|
end)
|
|
|
|
testing.expectEqual(status, true, storeName)
|
|
end
|
|
|
|
testing.registerGlobalTest('record stores', function()
|
|
for key, type in pairs(types) do
|
|
if type.records then
|
|
testRecordStore(type, key)
|
|
end
|
|
end
|
|
testRecordStore(core.magic.enchantments, "enchantments")
|
|
testRecordStore(core.magic.effects, "effects", true)
|
|
testRecordStore(core.magic.spells, "spells")
|
|
|
|
testRecordStore(core.stats.Attribute, "Attribute")
|
|
testRecordStore(core.stats.Skill, "Skill")
|
|
|
|
testRecordStore(core.sound, "sound")
|
|
testRecordStore(core.factions, "factions")
|
|
|
|
testRecordStore(types.NPC.classes, "classes")
|
|
testRecordStore(types.NPC.races, "races")
|
|
testRecordStore(types.Player.birthSigns, "birthSigns")
|
|
end)
|
|
|
|
testing.registerGlobalTest('record creation', function()
|
|
local newLight = {
|
|
isCarriable = true,
|
|
isDynamic = true,
|
|
isFire =false,
|
|
isFlicker = false,
|
|
isFlickerSlow = false,
|
|
isNegative = false,
|
|
isOffByDefault = false,
|
|
isPulse = false,
|
|
weight = 1,
|
|
value = 10,
|
|
duration = 12,
|
|
radius = 30,
|
|
color = 5,
|
|
name = "TestLight",
|
|
model = "meshes/marker_door.dae"
|
|
}
|
|
local draft = types.Light.createRecordDraft(newLight)
|
|
local record = world.createRecord(draft)
|
|
for key, value in pairs(newLight) do
|
|
testing.expectEqual(record[key], value)
|
|
end
|
|
end)
|
|
|
|
testing.registerGlobalTest('UTF-8 characters', function()
|
|
testing.expectEqual(utf8.codepoint("😀"), 0x1F600)
|
|
|
|
local chars = {}
|
|
|
|
for codepoint = 0, 0x10FFFF do
|
|
local char = utf8.char(codepoint)
|
|
local charSize = string.len(char)
|
|
|
|
testing.expect(not chars[char], nil, "Duplicate UTF-8 character: " .. char)
|
|
chars[char] = true
|
|
|
|
if codepoint <= 0x7F then
|
|
testing.expectEqual(charSize, 1)
|
|
elseif codepoint <= 0x7FF then
|
|
testing.expectEqual(charSize, 2)
|
|
elseif codepoint <= 0xFFFF then
|
|
testing.expectEqual(charSize, 3)
|
|
elseif codepoint <= 0x10FFFF then
|
|
testing.expectEqual(charSize, 4)
|
|
end
|
|
|
|
testing.expectEqual(utf8.codepoint(char), codepoint)
|
|
testing.expectEqual(utf8.len(char), 1)
|
|
end
|
|
end)
|
|
|
|
testing.registerGlobalTest('UTF-8 strings', function()
|
|
local utf8str = "Hello, 你好, 🌎!"
|
|
|
|
local str = ""
|
|
for utf_char in utf8str:gmatch(utf8.charpattern) do
|
|
str = str .. utf_char
|
|
end
|
|
testing.expectEqual(str, utf8str)
|
|
|
|
testing.expectEqual(utf8.len(utf8str), 13)
|
|
testing.expectEqual(utf8.offset(utf8str, 9), 11)
|
|
end)
|
|
|
|
testing.registerGlobalTest('memory limit', function()
|
|
local ok, err = pcall(function()
|
|
local t = {}
|
|
local n = 1
|
|
while true do
|
|
t[n] = n
|
|
n = n + 1
|
|
end
|
|
end)
|
|
testing.expectEqual(ok, false, 'Script reaching memory limit should fail')
|
|
testing.expectEqual(err, 'not enough memory')
|
|
end)
|
|
|
|
local function initPlayer()
|
|
local player = world.players[1]
|
|
player:teleport('', util.vector3(4096, 4096, 1745), util.transform.identity)
|
|
coroutine.yield()
|
|
return player
|
|
end
|
|
|
|
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')
|
|
testing.expectEqual(vfs.fileExists(nosuchfile), false, 'nosuchfile should not exist')
|
|
|
|
local expectedLines = { '1', '2', '', '4' }
|
|
local getLine = vfs.lines(file)
|
|
for _,v in pairs(expectedLines) do
|
|
testing.expectEqual(getLine(), v)
|
|
end
|
|
testing.expectEqual(getLine(), nil, 'All lines should have been read')
|
|
local ok = pcall(function()
|
|
vfs.lines(nosuchfile)
|
|
end)
|
|
testing.expectEqual(ok, false, 'Should not be able to read lines from nonexistent file')
|
|
|
|
local getPath = vfs.pathsWithPrefix('test_vfs_dir/')
|
|
testing.expectEqual(getPath(), file)
|
|
testing.expectEqual(getPath(), nil, 'All paths should have been read')
|
|
|
|
local handle = vfs.open(file)
|
|
testing.expectEqual(vfs.type(handle), 'file', 'File should be open')
|
|
testing.expectEqual(handle.fileName, file)
|
|
|
|
local n1, n2, _, l3, l4 = handle:read("*n", "*number", "*l", "*line", "*l")
|
|
testing.expectEqual(n1, 1)
|
|
testing.expectEqual(n2, 2)
|
|
testing.expectEqual(l3, '')
|
|
testing.expectEqual(l4, '4')
|
|
|
|
testing.expectEqual(handle:seek('set', 0), 0, 'Reading should happen from the start of the file')
|
|
testing.expectEqual(handle:read("*a"), '1\n2\n\n4')
|
|
|
|
testing.expectEqual(handle:close(), true, 'File should be closeable')
|
|
testing.expectEqual(vfs.type(handle), 'closed file', 'File should be closed')
|
|
|
|
handle = vfs.open(nosuchfile)
|
|
testing.expectEqual(handle, nil, 'vfs.open should return nil on nonexistent files')
|
|
|
|
getLine = vfs.open(file):lines()
|
|
for _,v in pairs(expectedLines) do
|
|
testing.expectEqual(getLine(), v)
|
|
end
|
|
end)
|
|
|
|
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
|
|
types.Player.setCrimeLevel(player, 0)
|
|
testing.expectEqual(I.Crimes.commitCrime(player, { type = types.Player.OFFENSE_TYPE.Theft, victim = player, arg = 100}).wasCrimeSeen, false, "Running the crime with the player as the victim should not result in a seen crime")
|
|
testing.expectEqual(I.Crimes.commitCrime(player, { type = types.Player.OFFENSE_TYPE.Theft, arg = 50 }).wasCrimeSeen, false, "Running the crime with no victim and a type shouldn't raise errors")
|
|
testing.expectEqual(I.Crimes.commitCrime(player, { type = types.Player.OFFENSE_TYPE.Murder }).wasCrimeSeen, false, "Running a murder crime should work even without a victim")
|
|
|
|
-- Create a mockup target for crimes
|
|
local victim = world.createObject(types.NPC.record(player).id)
|
|
victim:teleport(player.cell, player.position + util.vector3(0, 300, 0))
|
|
coroutine.yield()
|
|
|
|
-- Reset crime level for testing with a valid victim
|
|
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)
|
|
|
|
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
|
|
|
|
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.updateGlobal,
|
|
},
|
|
eventHandlers = testing.globalEventHandlers,
|
|
}
|