Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
11 kB · 384 lines
Lua
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385local core = require('openmw.core')local util = require('openmw.util')
local M = {}
local menuTestsOrder = {}local menuTests = {}local discoveredTests = {}local setupGlobalTest = function() end
local globalTestsOrder = {}local globalTests = {}local globalTestRunner = nillocal currentGlobalTest = {}local setupLocalTest = function() endlocal discoveredLocalTests = {}
local localTestsOrder = {}local localTests = {}local localTestRunner = nillocal currentLocalTest = {}
local localDiscoveryTimeout = 10local globalDiscoveryTimeout = localDiscoveryTimeout + 10
local function resumeChecked(co) local ok, err = coroutine.resume(co) if not ok then print('TEST_ERROR', tostring(err)) core.quit() endend
local function makeTestCoroutine(fn) local co = coroutine.create(fn) return function() if coroutine.status(co) ~= 'dead' then resumeChecked(co) end endend
local function waitForDiscovery(mailbox, timeout, request, errMsg) mailbox.value = nil request() while mailbox.value == nil do timeout = timeout - 1 if timeout == 0 then error(errMsg) end coroutine.yield() end return mailbox.valueend
local function runRemoteTest(state, name, request) state.name = name state.error = nil request() while state.name do coroutine.yield() end if state.error then error(state.error, 0) endend
local function finishRemoteTest(state, data, eventName) if data.name ~= state.name then error(string.format('%s with incorrect name %s, expected %s', eventName, data.name, state.name), 2) end state.name = nil state.error = data.errMsgend
local function loadTestConfig() local hasTestConfig, testConfig = pcall(require, 'test_config') if not hasTestConfig then if not tostring(testConfig):find('module not found') then error(testConfig) end return {} end return testConfigend
local testConfig = loadTestConfig()
local function testNameMatchesFilter(name) return testConfig.filter == nil or name:find(testConfig.filter) ~= nilend
local function runTests(tests) for i, test in ipairs(tests) do if testNameMatchesFilter(test.name) then print('TEST_START', i, test.name) local status, err = pcall(test.fn) if status then print('TEST_OK', i, test.name) else print('TEST_FAILED', i, test.name, err) end end end core.quit()end
function M.makeUpdateMenu() return makeTestCoroutine(function() local menu = require('openmw.menu') print('Discovering tests...') menu.newGame({bypass = true}) coroutine.yield() local tests = waitForDiscovery(discoveredTests, globalDiscoveryTimeout, function() core.sendGlobalEvent('discoverTests') end, 'test discovery timed out: no testsDiscovered event from the global script') print('Discovered', #tests, 'tests') for _, name in ipairs(tests) do if menuTests[name] then error('discovered global/local test name collides with a menu test: ' .. name) end table.insert(menuTestsOrder, { name = name, fn = function() setupGlobalTest() M.runTest(name) end, }) end if testConfig.list == true then for i, t in ipairs(menuTestsOrder) do if testNameMatchesFilter(t.name) then print('TEST_FOUND', i, t.name) end end core.quit() return end print('Running tests...') runTests(menuTestsOrder) end)end
function M.registerMenuTest(name, fn) menuTests[name] = fn table.insert(menuTestsOrder, {name = name, fn = fn})end
function M.setSetupGlobalTest(fn) setupGlobalTest = fnend
function M.runGlobalTest(name) runRemoteTest(currentGlobalTest, name, function() core.sendGlobalEvent('runGlobalTest', name) end)end
function M.runTest(name) runRemoteTest(currentGlobalTest, name, function() core.sendGlobalEvent('runTest', name) end)end
function M.registerGlobalTest(name, fn) globalTests[name] = fn table.insert(globalTestsOrder, name)end
function M.registerGlobalTestStep(name, fn) globalTests[name] = fnend
function M.setSetupLocalTest(fn) setupLocalTest = fnend
function M.updateGlobal() if globalTestRunner and coroutine.status(globalTestRunner) ~= 'dead' then resumeChecked(globalTestRunner) else globalTestRunner = nil endend
function M.runLocalTest(obj, name, arg) runRemoteTest(currentLocalTest, name, function() obj:sendEvent('runLocalTest', {name=name, arg=arg}) end)end
function M.registerLocalTest(name, fn) localTests[name] = fn table.insert(localTestsOrder, name)end
function M.registerLocalTestStep(name, fn) localTests[name] = fnend
function M.updateLocal() if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then if not core.isWorldPaused() then resumeChecked(localTestRunner) end else localTestRunner = nil endend
function M.expect(cond, msg) if not cond then error(msg or '"true" expected', 2) endend
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) endend
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) endend
function M.expectGreaterOrEqual(v1, v2, msg) if not (v1 >= v2) then error(string.format('%s: %f >= %f', msg or '', v1, v2), 2) endend
function M.expectGreaterThan(v1, v2, msg) if not (v1 > v2) then error(string.format('%s: %s > %s', msg or '', v1, v2), 2) endend
function M.expectLessOrEqual(v1, v2, msg) if not (v1 <= v2) then error(string.format('%s: %s <= %s', msg or '', v1, v2), 2) endend
function M.expectEqual(v1, v2, msg) if not (v1 == v2) then error(string.format('%s: %s ~= %s', msg or '', v1, v2), 2) endend
function M.expectNotEqual(v1, v2, msg) if v1 == v2 then error(string.format('%s: %s == %s', msg or '', v1, v2), 2) endend
----- Verifies that given value matches provided matcher.-- @function expectThat-- @param value#any any value to match.-- @param matcher#function a function returing empty string in the case of success or a message explaining the mismatch.-- @param msg#string a message to prefix failure reason.-- @usage-- local matcher = function(actual)-- if actual == 42 then-- return ''-- end-- return string.format('%s is not 42', actual)-- end-- expectThat(42, matcher)function M.expectThat(value, matcher, msg) local message = matcher(value) if message ~= '' then error(string.format('%s: actual does not match expected: %s', msg or 'Failure', message), 2) endend
function M.formatActualExpected(actual, expected) return string.format('actual: %s, expected: %s', actual, expected)end
-- used only in menu scriptsM.menuEventHandlers = { globalTestFinished = function(data) finishRemoteTest(currentGlobalTest, data, 'globalTestFinished') end, testsDiscovered = function(data) discoveredTests.value = data.tests end,}
-- used only in global scriptsM.globalEventHandlers = { runTest = function(name) local types = require('openmw.types') local world = require('openmw.world') local fn = globalTests[name] if fn then 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) else globalTestRunner = coroutine.create(function() local player = world.players[1] local status, err = pcall(function() setupLocalTest(player) M.runLocalTest(player, name) end) if status then err = nil end types.Player.sendMenuEvent(world.players[1], 'globalTestFinished', {name=name, errMsg=err}) end) end end, runGlobalTest = function(name) local 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) finishRemoteTest(currentLocalTest, data, 'localTestFinished') end, discoverTests = function() globalTestRunner = coroutine.create(function() local types = require('openmw.types') local world = require('openmw.world') local localTestNames = waitForDiscovery(discoveredLocalTests, localDiscoveryTimeout, function() world.players[1]:sendEvent('discoverLocalTests') end, 'local test discovery timed out: no localTestsDiscovered event from the local script') for _, name in ipairs(localTestNames) do if globalTests[name] then error('local test name collides with a global test/step: ' .. name) end end local tests = {} for _, name in ipairs(globalTestsOrder) do table.insert(tests, name) end for _, name in ipairs(localTestNames) do table.insert(tests, name) end types.Player.sendMenuEvent(world.players[1], 'testsDiscovered', {tests = tests}) end) end, localTestsDiscovered = function(data) discoveredLocalTests.value = data.tests end,}
-- used only in local scriptsM.localEventHandlers = { runLocalTest = function(data) local name = data.name local fn = localTests[name] if not fn then core.sendGlobalEvent('localTestFinished', {name=name, errMsg='Local test is not found'}) return end localTestRunner = coroutine.create(function() local status, err = pcall(fn, data.arg) if status then err = nil end core.sendGlobalEvent('localTestFinished', {name=name, errMsg=err}) end) end, discoverLocalTests = function() core.sendGlobalEvent('localTestsDiscovered', {tests = localTestsOrder}) end,}
return M