mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-29 22:20:33 +00:00
Introduce OpenMW integration tests
This commit is contained in:
parent
9b0928e8f4
commit
76160076db
63
scripts/data/integration_tests/test_lua_api/player.lua
Normal file
63
scripts/data/integration_tests/test_lua_api/player.lua
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
local testing = require('testing_util')
|
||||||
|
local self = require('openmw.self')
|
||||||
|
local util = require('openmw.util')
|
||||||
|
local core = require('openmw.core')
|
||||||
|
local input = require('openmw.input')
|
||||||
|
local types = require('openmw.types')
|
||||||
|
|
||||||
|
input.setControlSwitch(input.CONTROL_SWITCH.Fighting, false)
|
||||||
|
input.setControlSwitch(input.CONTROL_SWITCH.Jumping, false)
|
||||||
|
input.setControlSwitch(input.CONTROL_SWITCH.Looking, false)
|
||||||
|
input.setControlSwitch(input.CONTROL_SWITCH.Magic, false)
|
||||||
|
input.setControlSwitch(input.CONTROL_SWITCH.VanityMode, false)
|
||||||
|
input.setControlSwitch(input.CONTROL_SWITCH.ViewMode, false)
|
||||||
|
|
||||||
|
testing.registerLocalTest('playerMovement',
|
||||||
|
function()
|
||||||
|
local startTime = core.getSimulationTime()
|
||||||
|
local pos = self.position
|
||||||
|
|
||||||
|
while core.getSimulationTime() < startTime + 0.5 do
|
||||||
|
self.controls.jump = false
|
||||||
|
self.controls.run = true
|
||||||
|
self.controls.movement = 0
|
||||||
|
self.controls.sideMovement = 0
|
||||||
|
local progress = (core.getSimulationTime() - startTime) / 0.5
|
||||||
|
self.controls.yawChange = util.normalizeAngle(math.rad(90) * progress - self.rotation.z)
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
testing.expectEqualWithDelta(self.rotation.z, math.rad(90), 0.05, 'Incorrect rotation')
|
||||||
|
|
||||||
|
while core.getSimulationTime() < startTime + 1.5 do
|
||||||
|
self.controls.jump = false
|
||||||
|
self.controls.run = true
|
||||||
|
self.controls.movement = 1
|
||||||
|
self.controls.sideMovement = 0
|
||||||
|
self.controls.yawChange = 0
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
direction = (self.position - pos) / types.Actor.runSpeed(self)
|
||||||
|
testing.expectEqualWithDelta(direction.x, 1, 0.1, 'Run forward, X coord')
|
||||||
|
testing.expectEqualWithDelta(direction.y, 0, 0.1, 'Run forward, Y coord')
|
||||||
|
|
||||||
|
pos = self.position
|
||||||
|
while core.getSimulationTime() < startTime + 2.5 do
|
||||||
|
self.controls.jump = false
|
||||||
|
self.controls.run = false
|
||||||
|
self.controls.movement = -1
|
||||||
|
self.controls.sideMovement = -1
|
||||||
|
self.controls.yawChange = 0
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
direction = (self.position - pos) / types.Actor.walkSpeed(self)
|
||||||
|
testing.expectEqualWithDelta(direction.x, -0.707, 0.1, 'Walk diagonally, X coord')
|
||||||
|
testing.expectEqualWithDelta(direction.y, 0.707, 0.1, 'Walk diagonally, Y coord')
|
||||||
|
end)
|
||||||
|
|
||||||
|
return {
|
||||||
|
engineHandlers = {
|
||||||
|
onUpdate = testing.updateLocal,
|
||||||
|
},
|
||||||
|
eventHandlers = testing.eventHandlers
|
||||||
|
}
|
||||||
|
|
67
scripts/data/integration_tests/test_lua_api/test.lua
Normal file
67
scripts/data/integration_tests/test_lua_api/test.lua
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
local testing = require('testing_util')
|
||||||
|
local core = require('openmw.core')
|
||||||
|
local async = require('openmw.async')
|
||||||
|
local util = require('openmw.util')
|
||||||
|
|
||||||
|
local function testTimers()
|
||||||
|
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.expectAlmostEqual(th1, 36, 'async:newGameTimer failed')
|
||||||
|
testing.expectAlmostEqual(ts1, 0.5, 'async:newSimulationTimer failed')
|
||||||
|
testing.expectAlmostEqual(th2, 72, 'async:newUnsavableGameTimer failed')
|
||||||
|
testing.expectAlmostEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function testTeleport()
|
||||||
|
player:teleport('', util.vector3(100, 50, 0), util.vector3(0, 0, 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.rotation.z, math.rad(-90), 0.05, 'incorrect rotation after teleporting')
|
||||||
|
|
||||||
|
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.z, math.rad(-90), 0.05, 'teleporting changes rotation')
|
||||||
|
end
|
||||||
|
|
||||||
|
tests = {
|
||||||
|
{'timers', testTimers},
|
||||||
|
{'playerMovement', function() testing.runLocalTest(player, 'playerMovement') end},
|
||||||
|
{'teleport', testTeleport},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
engineHandlers = {
|
||||||
|
onUpdate = testing.testRunner(tests),
|
||||||
|
onPlayerAdded = function(p) player = p end,
|
||||||
|
},
|
||||||
|
eventHandlers = testing.eventHandlers,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
GLOBAL: test.lua
|
||||||
|
PLAYER: player.lua
|
||||||
|
|
93
scripts/data/integration_tests/test_lua_api/testing_util.lua
Normal file
93
scripts/data/integration_tests/test_lua_api/testing_util.lua
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
local core = require('openmw.core')
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
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 co = coroutine.create(fn)
|
||||||
|
return function()
|
||||||
|
if coroutine.status(co) ~= 'dead' then
|
||||||
|
coroutine.resume(co)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.runLocalTest(obj, name)
|
||||||
|
currentLocalTest = name
|
||||||
|
currentLocalTestError = nil
|
||||||
|
obj:sendEvent('runLocalTest', name)
|
||||||
|
while currentLocalTest do coroutine.yield() end
|
||||||
|
if currentLocalTestError then error(currentLocalTestError, 2) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.expect(cond, delta, msg)
|
||||||
|
if not cond then
|
||||||
|
error(msg or '"true" expected', 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.expectEqualWithDelta(v1, v2, delta, msg)
|
||||||
|
if math.abs(v1 - v2) > delta then
|
||||||
|
error(string.format('%s: %f ~= %f', msg or '', v1, v2), 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.expectAlmostEqual(v1, v2, msg)
|
||||||
|
if math.abs(v1 - v2) / (math.abs(v1) + math.abs(v2)) > 0.05 then
|
||||||
|
error(string.format('%s: %f ~= %f', msg or '', v1, v2), 2)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
coroutine.resume(localTestRunner)
|
||||||
|
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'})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
localTestRunner = coroutine.create(function()
|
||||||
|
local status, err = pcall(fn)
|
||||||
|
if status then err = nil end
|
||||||
|
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
|
||||||
|
|
87
scripts/integration_tests.py
Executable file
87
scripts/integration_tests.py
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse, datetime, os, subprocess, sys, shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="OpenMW integration tests.")
|
||||||
|
parser.add_argument(
|
||||||
|
"example_suite",
|
||||||
|
type=str,
|
||||||
|
help="path to openmw example suite (use 'git clone https://gitlab.com/OpenMW/example-suite/' to get it)",
|
||||||
|
)
|
||||||
|
parser.add_argument("--omw", type=str, default="openmw", help="path to openmw binary")
|
||||||
|
parser.add_argument(
|
||||||
|
"--workdir", type=str, default="integration_tests_output", help="directory for temporary files and logs"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
example_suite_dir = Path(args.example_suite).resolve()
|
||||||
|
example_suite_content = example_suite_dir / "game_template" / "data" / "template.omwgame"
|
||||||
|
if not example_suite_content.is_file():
|
||||||
|
sys.exit(
|
||||||
|
f"{example_suite_content} not found, use 'git clone https://gitlab.com/OpenMW/example-suite/' to get it"
|
||||||
|
)
|
||||||
|
|
||||||
|
openmw_binary = Path(args.omw).resolve()
|
||||||
|
if not openmw_binary.is_file():
|
||||||
|
sys.exit(f"{openmw_binary} not found")
|
||||||
|
|
||||||
|
work_dir = Path(args.workdir).resolve()
|
||||||
|
work_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
config_dir = work_dir / "config"
|
||||||
|
userdata_dir = work_dir / "userdata"
|
||||||
|
tests_dir = Path(__file__).resolve().parent / "data" / "integration_tests"
|
||||||
|
time_str = datetime.datetime.now().strftime("%Y-%m-%d-%H.%M.%S")
|
||||||
|
|
||||||
|
|
||||||
|
def runTest(name):
|
||||||
|
print(f"Start {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
|
||||||
|
with open(config_dir / "openmw.cfg", "w", encoding="utf-8") as omw_cfg:
|
||||||
|
omw_cfg.writelines(
|
||||||
|
(
|
||||||
|
f'data="{example_suite_dir}{os.sep}game_template{os.sep}data"\n',
|
||||||
|
f'data-local="{test_dir}"\n',
|
||||||
|
f'user-data="{userdata_dir}"\n',
|
||||||
|
"content=template.omwgame\n",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (test_dir / "test.omwscripts").exists():
|
||||||
|
omw_cfg.write("content=test.omwscripts\n")
|
||||||
|
with subprocess.Popen(
|
||||||
|
[f"{openmw_binary}", "--replace=config", f"--config={config_dir}", "--skip-menu", "--no-grab"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
encoding="utf-8",
|
||||||
|
) as process:
|
||||||
|
quit_requested = False
|
||||||
|
for line in process.stdout:
|
||||||
|
words = line.split(" ")
|
||||||
|
if len(words) > 1 and words[1] == "E]":
|
||||||
|
print(line, end="")
|
||||||
|
elif "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="")
|
||||||
|
elif "TEST_OK" in line:
|
||||||
|
print(f"OK")
|
||||||
|
elif "TEST_FAILED" in line:
|
||||||
|
w = line.split("TEST_FAILED")[1].split("\t")
|
||||||
|
print(f"FAILED {w[3]}\t\t")
|
||||||
|
process.wait(5)
|
||||||
|
if not quit_requested:
|
||||||
|
print("ERROR: Unexpected termination")
|
||||||
|
shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log")
|
||||||
|
print(f"{name} finished")
|
||||||
|
|
||||||
|
|
||||||
|
for entry in tests_dir.glob("test_*"):
|
||||||
|
if entry.is_dir():
|
||||||
|
runTest(entry.name)
|
||||||
|
shutil.rmtree(config_dir, ignore_errors=True)
|
||||||
|
shutil.rmtree(userdata_dir, ignore_errors=True)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user