Skip to content

Commit 7989d16

Browse files
committed
Run integration tests in CI
1 parent 8a13cde commit 7989d16

File tree

9 files changed

+150
-29
lines changed

9 files changed

+150
-29
lines changed

.gitlab-ci.yml

+19-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ default:
55
# See https://docs.gitlab.com/ee/ci/yaml/#needs
66
stages:
77
- build
8+
- test
89

910
# https://blog.nimbleways.com/let-s-make-faster-gitlab-ci-cd-pipelines/
1011
variables:
@@ -21,7 +22,6 @@ variables:
2122
image: ubuntu:focal
2223
rules:
2324
- if: $CI_PIPELINE_SOURCE == "push"
24-
2525

2626
.Ubuntu:
2727
extends: .Ubuntu_Image
@@ -300,6 +300,24 @@ Ubuntu_Clang_tests_Debug:
300300
reports:
301301
junit: build/tests.xml
302302

303+
Ubuntu_Clang_integration_tests:
304+
extends: .Ubuntu_Image
305+
stage: test
306+
needs:
307+
- Ubuntu_Clang
308+
variables:
309+
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
310+
cache:
311+
key: Ubuntu_Clang_integration_tests.v1
312+
paths:
313+
- .cache/pip
314+
- apt-cache/
315+
before_script:
316+
- CI/install_debian_deps.sh openmw-integration-tests
317+
- pip3 install --user numpy matplotlib termtables click
318+
script:
319+
- CI/run_integration_tests.sh
320+
303321
.MacOS:
304322
image: macos-11-xcode-12
305323
tags:

CI/before_script.linux.sh

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ declare -a CMAKE_CONF_OPTS=(
2828
-DBUILD_SHARED_LIBS=OFF
2929
-DUSE_SYSTEM_TINYXML=ON
3030
-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON
31-
-DCMAKE_INSTALL_PREFIX=install
3231
-DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project
3332
)
3433

CI/install_debian_deps.sh

+33
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,39 @@ declare -rA GROUPED_DEPS=(
4242
"
4343

4444
[openmw-coverage]="gcovr"
45+
46+
[openmw-integration-tests]="
47+
ca-certificates
48+
git
49+
git-lfs
50+
libavcodec58
51+
libavformat58
52+
libavutil56
53+
libboost-filesystem1.71.0
54+
libboost-iostreams1.71.0
55+
libboost-program-options1.71.0
56+
libboost-system1.71.0
57+
libbullet2.88
58+
libcollada-dom2.4-dp0
59+
libicu66
60+
libjpeg8
61+
libluajit-5.1-2
62+
liblz4-1
63+
libmyguiengine3debian1v5
64+
libopenal1
65+
libopenscenegraph161
66+
libpng16-16
67+
libqt5opengl5
68+
librecast1
69+
libsdl2-2.0-0
70+
libsqlite3-0
71+
libswresample3
72+
libswscale5
73+
libtinyxml2.6.2v5
74+
libyaml-cpp0.6
75+
python3-pip
76+
xvfb
77+
"
4578
)
4679

4780
if [[ $# -eq 0 ]]; then

CI/run_integration_tests.sh

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash -ex
2+
3+
git clone --depth=1 https://gitlab.com/OpenMW/example-suite.git
4+
5+
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \
6+
scripts/integration_tests.py --omw build/install/bin/openmw --workdir integration_tests_output example-suite/
7+
8+
ls integration_tests_output/*.osg_stats.log | while read v; do
9+
scripts/osg_stats.py --stats '.*' --regexp_match < "${v}"
10+
done

scripts/data/integration_tests/test_lua_api/player.lua

+25-16
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,56 @@ input.setControlSwitch(input.CONTROL_SWITCH.Magic, false)
1212
input.setControlSwitch(input.CONTROL_SWITCH.VanityMode, false)
1313
input.setControlSwitch(input.CONTROL_SWITCH.ViewMode, false)
1414

15-
testing.registerLocalTest('playerMovement',
15+
testing.registerLocalTest('playerRotation',
1616
function()
17-
local startTime = core.getSimulationTime()
18-
local pos = self.position
19-
20-
while core.getSimulationTime() < startTime + 0.5 do
17+
local endTime = core.getSimulationTime() + 1
18+
while core.getSimulationTime() < endTime do
2119
self.controls.jump = false
2220
self.controls.run = true
2321
self.controls.movement = 0
2422
self.controls.sideMovement = 0
25-
local progress = (core.getSimulationTime() - startTime) / 0.5
26-
self.controls.yawChange = util.normalizeAngle(math.rad(90) * progress - self.rotation.z)
23+
self.controls.yawChange = util.normalizeAngle(math.rad(90) - self.rotation.z) * 0.5
2724
coroutine.yield()
2825
end
2926
testing.expectEqualWithDelta(self.rotation.z, math.rad(90), 0.05, 'Incorrect rotation')
27+
end)
3028

31-
while core.getSimulationTime() < startTime + 1.5 do
29+
testing.registerLocalTest('playerForwardRunning',
30+
function()
31+
local startPos = self.position
32+
local endTime = core.getSimulationTime() + 1
33+
while core.getSimulationTime() < endTime do
3234
self.controls.jump = false
3335
self.controls.run = true
3436
self.controls.movement = 1
3537
self.controls.sideMovement = 0
3638
self.controls.yawChange = 0
3739
coroutine.yield()
3840
end
39-
direction = (self.position - pos) / types.Actor.runSpeed(self)
40-
testing.expectEqualWithDelta(direction.x, 1, 0.1, 'Run forward, X coord')
41-
testing.expectEqualWithDelta(direction.y, 0, 0.1, 'Run forward, Y coord')
41+
local direction, distance = (self.position - startPos):normalize()
42+
local normalizedDistance = distance / types.Actor.runSpeed(self)
43+
testing.expectEqualWithDelta(normalizedDistance, 1, 0.2, 'Normalized forward runned distance')
44+
testing.expectEqualWithDelta(direction.x, 0, 0.1, 'Run forward, X coord')
45+
testing.expectEqualWithDelta(direction.y, 1, 0.1, 'Run forward, Y coord')
46+
end)
4247

43-
pos = self.position
44-
while core.getSimulationTime() < startTime + 2.5 do
48+
testing.registerLocalTest('playerDiagonalWalking',
49+
function()
50+
local startPos = self.position
51+
local endTime = core.getSimulationTime() + 1
52+
while core.getSimulationTime() < endTime do
4553
self.controls.jump = false
4654
self.controls.run = false
4755
self.controls.movement = -1
4856
self.controls.sideMovement = -1
4957
self.controls.yawChange = 0
5058
coroutine.yield()
5159
end
52-
direction = (self.position - pos) / types.Actor.walkSpeed(self)
60+
local direction, distance = (self.position - startPos):normalize()
61+
local normalizedDistance = distance / types.Actor.walkSpeed(self)
62+
testing.expectEqualWithDelta(normalizedDistance, 1, 0.2, 'Normalized diagonally walked distance')
5363
testing.expectEqualWithDelta(direction.x, -0.707, 0.1, 'Walk diagonally, X coord')
54-
testing.expectEqualWithDelta(direction.y, 0.707, 0.1, 'Walk diagonally, Y coord')
64+
testing.expectEqualWithDelta(direction.y, -0.707, 0.1, 'Walk diagonally, Y coord')
5565
end)
5666

5767
return {
@@ -60,4 +70,3 @@ return {
6070
},
6171
eventHandlers = testing.eventHandlers
6272
}
63-

scripts/data/integration_tests/test_lua_api/test.lua

+21-5
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ local function testTimers()
2929

3030
while not (ts1 and ts2 and th1 and th2) do coroutine.yield() end
3131

32-
testing.expectAlmostEqual(th1, 36, 'async:newGameTimer failed')
33-
testing.expectAlmostEqual(ts1, 0.5, 'async:newSimulationTimer failed')
34-
testing.expectAlmostEqual(th2, 72, 'async:newUnsavableGameTimer failed')
35-
testing.expectAlmostEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed')
32+
testing.expectGreaterOrEqual(th1, 36, 'async:newGameTimer failed')
33+
testing.expectGreaterOrEqual(ts1, 0.5, 'async:newSimulationTimer failed')
34+
testing.expectGreaterOrEqual(th2, 72, 'async:newUnsavableGameTimer failed')
35+
testing.expectGreaterOrEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed')
3636
end
3737

3838
local function testTeleport()
@@ -51,9 +51,25 @@ local function testTeleport()
5151
testing.expectEqualWithDelta(player.rotation.z, math.rad(-90), 0.05, 'teleporting changes rotation')
5252
end
5353

54+
local function initPlayer()
55+
player:teleport('', util.vector3(4096, 4096, 867.237), util.vector3(0, 0, 0))
56+
coroutine.yield()
57+
end
58+
5459
tests = {
5560
{'timers', testTimers},
56-
{'playerMovement', function() testing.runLocalTest(player, 'playerMovement') end},
61+
{'playerRotation', function()
62+
initPlayer()
63+
testing.runLocalTest(player, 'playerRotation')
64+
end},
65+
{'playerForwardRunning', function()
66+
initPlayer()
67+
testing.runLocalTest(player, 'playerForwardRunning')
68+
end},
69+
{'playerDiagonalWalking', function()
70+
initPlayer()
71+
testing.runLocalTest(player, 'playerDiagonalWalking')
72+
end},
5773
{'teleport', testTeleport},
5874
}
5975

scripts/data/integration_tests/test_lua_api/testing_util.lua

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ function M.expectAlmostEqual(v1, v2, msg)
5252
end
5353
end
5454

55+
function M.expectGreaterOrEqual(v1, v2, msg)
56+
if not (v1 >= v2) then
57+
error(string.format('%s: %f >= %f', msg or '', v1, v2), 2)
58+
end
59+
end
60+
5561
local localTests = {}
5662
local localTestRunner = nil
5763

scripts/integration_tests.py

+35-5
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,30 @@ def runTest(name):
5151
)
5252
if (test_dir / "test.omwscripts").exists():
5353
omw_cfg.write("content=test.omwscripts\n")
54+
with open(config_dir / "settings.cfg", "a", encoding="utf-8") as settings_cfg:
55+
settings_cfg.write(
56+
"[Video]\n"
57+
"resolution x = 640\n"
58+
"resolution y = 480\n"
59+
"framerate limit = 60\n"
60+
)
61+
stdout_lines = list()
62+
exit_ok = True
63+
test_success = True
5464
with subprocess.Popen(
55-
[f"{openmw_binary}", "--replace=config", f"--config={config_dir}", "--skip-menu", "--no-grab"],
65+
[openmw_binary, "--replace=config", "--config", config_dir, "--skip-menu", "--no-grab", "--no-sound"],
5666
stdout=subprocess.PIPE,
5767
stderr=subprocess.STDOUT,
5868
encoding="utf-8",
69+
env={
70+
"OPENMW_OSG_STATS_FILE": work_dir / f"{name}.{time_str}.osg_stats.log",
71+
"OPENMW_OSG_STATS_LIST": "times",
72+
**os.environ,
73+
},
5974
) as process:
6075
quit_requested = False
6176
for line in process.stdout:
77+
stdout_lines.append(line)
6278
words = line.split(" ")
6379
if len(words) > 1 and words[1] == "E]":
6480
print(line, end="")
@@ -72,16 +88,30 @@ def runTest(name):
7288
elif "TEST_FAILED" in line:
7389
w = line.split("TEST_FAILED")[1].split("\t")
7490
print(f"FAILED {w[3]}\t\t")
91+
test_success = False
7592
process.wait(5)
7693
if not quit_requested:
7794
print("ERROR: Unexpected termination")
78-
shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log")
79-
print(f"{name} finished")
95+
exit_ok = False
96+
if process.returncode != 0:
97+
print(f"ERROR: openmw exited with code {process.returncode}")
98+
exit_ok = False
99+
if os.path.exists(config_dir / "openmw.log"):
100+
shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log")
101+
if not exit_ok:
102+
sys.stdout.writelines(stdout_lines)
103+
if test_success and exit_ok:
104+
print(f"{name} succeeded")
105+
else:
106+
print(f"{name} failed")
107+
return test_success and exit_ok
80108

81109

110+
status = 0
82111
for entry in tests_dir.glob("test_*"):
83112
if entry.is_dir():
84-
runTest(entry.name)
113+
if not runTest(entry.name):
114+
status = -1
85115
shutil.rmtree(config_dir, ignore_errors=True)
86116
shutil.rmtree(userdata_dir, ignore_errors=True)
87-
117+
exit(status)

scripts/osg_stats.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo
7676
def matching_keys(patterns):
7777
if regexp_match:
7878
return [key for pattern in patterns for key in keys if re.search(pattern, key)]
79-
return keys
79+
return patterns
8080
if timeseries:
8181
draw_timeseries(sources=frames, keys=matching_keys(timeseries), add_sum=timeseries_sum,
8282
begin_frame=begin_frame, end_frame=end_frame)

0 commit comments

Comments
 (0)