-- Eventloop behavior tests for the CraftOS-PC harness. local createEventLoop = require('/apis/eventloop'); local createLibTest = require('/apis/libtest'); local testlib = createLibTest({ ... }); testlib.test('register dispatches queued event args', function() local events = createEventLoop(); local called = 0; events.register('eventloop_test_basic', function(a, b) called = called + 1; testlib.assertEquals(a, 'first'); testlib.assertEquals(b, 42); events.stopLoop(); end); os.queueEvent('eventloop_test_basic', 'first', 42); events.runLoop(); testlib.assertEquals(called, 1); testlib.assertTrue(not events.isRunningLoop()); end); testlib.test('STOP unregisters handler after first call', function() local events = createEventLoop(); local stoppedHandlerCalls = 0; local observerCalls = 0; events.register('eventloop_test_stop', function() stoppedHandlerCalls = stoppedHandlerCalls + 1; return events.STOP; end); events.register('eventloop_test_stop', function() observerCalls = observerCalls + 1; if observerCalls == 1 then os.queueEvent('eventloop_test_stop'); else events.stopLoop(); end end); os.queueEvent('eventloop_test_stop'); events.runLoop(); testlib.assertEquals(stoppedHandlerCalls, 1); testlib.assertEquals(observerCalls, 2); end); testlib.test('manual unregister prevents dispatch', function() local events = createEventLoop(); local called = 0; local dispose = events.register('eventloop_test_unregister', function() called = called + 1; end); events.setTimeout(function() events.stopLoop(); end, 0); dispose(); os.queueEvent('eventloop_test_unregister'); events.runLoop(); testlib.assertEquals(called, 0); end); testlib.test('setTimeout before runLoop fires once', function() local events = createEventLoop(); local called = 0; events.setTimeout(function() called = called + 1; events.stopLoop(); end, 0); events.runLoop(); testlib.assertEquals(called, 1); end); testlib.test('setTimeout during runLoop fires after event handler', function() local events = createEventLoop(); local order = ''; events.register('eventloop_test_runtime_timeout', function() order = order .. 'event>'; events.setTimeout(function() order = order .. 'timeout'; events.stopLoop(); end, 0); return events.STOP; end); os.queueEvent('eventloop_test_runtime_timeout'); events.runLoop(); testlib.assertEquals(order, 'event>timeout'); end); testlib.test('cleared timeout before runLoop does not fire', function() local events = createEventLoop(); local called = 0; local clear = events.setTimeout(function() called = called + 1; end, 0); clear(); events.setTimeout(function() events.stopLoop(); end, 0); events.runLoop(); testlib.assertEquals(called, 0); end); testlib.test('onStart and onStop run around loop', function() local events = createEventLoop(); local order = ''; events.onStart(function() order = order .. 'start>'; end); events.onStop(function() order = order .. 'stop'; end); events.setTimeout(function() order = order .. 'timeout>'; events.stopLoop(); end, 0); events.runLoop(); testlib.assertEquals(order, 'start>timeout>stop'); end); testlib.test('same-loop stop does not leak to a sibling loop with the same id', function() local createEventLoopA = assert(loadfile('/apis/eventloop.lua'))(); local createEventLoopB = assert(loadfile('/apis/eventloop.lua'))(); local bootEvents = createEventLoopA(); local verifierEvents = createEventLoopB(); local bootProbeHandled = false; bootEvents.register('eventloop_test_same_loop_probe', function() bootProbeHandled = true; bootEvents.stopLoop(); end); verifierEvents.register('eventloop_test_same_loop_stop', function() verifierEvents.stopLoop(); os.queueEvent('eventloop_test_same_loop_probe'); end); os.queueEvent('eventloop_test_same_loop_stop'); parallel.waitForAll(function() bootEvents.runLoop(true); end, function() verifierEvents.runLoop(); end); testlib.assertTrue(bootProbeHandled); end); testlib.test('external stop events use ids unique across module instances', function() local createEventLoopA = assert(loadfile('/apis/eventloop.lua'))(); local createEventLoopB = assert(loadfile('/apis/eventloop.lua'))(); local bootEvents = createEventLoopA(); local verifierEvents = createEventLoopB(); local bootProbeHandled = false; bootEvents.register('eventloop_test_external_stop', function() verifierEvents.stopLoop(); os.queueEvent('eventloop_test_external_probe'); end); bootEvents.register('eventloop_test_external_probe', function() bootProbeHandled = true; bootEvents.stopLoop(); end); os.queueEvent('eventloop_test_external_stop'); parallel.waitForAll(function() bootEvents.runLoop(true); end, function() verifierEvents.runLoop(true); end); testlib.assertTrue(bootProbeHandled); end); testlib.test('protected dispatch reports errors and keeps the loop alive', function() local errors = {}; local events = createEventLoop({ onError = function(eventName, err) errors[#errors + 1] = { eventName = eventName, err = err }; end, }); local siblingCalled = false; local laterCalled = false; events.register('eventloop_test_protected', function() error('handler boom', 0); end); events.register('eventloop_test_protected', function() siblingCalled = true; os.queueEvent('eventloop_test_protected_later'); end); events.register('eventloop_test_protected_later', function() laterCalled = true; events.stopLoop(); end); os.queueEvent('eventloop_test_protected'); events.runLoop(); testlib.assertEquals(#errors, 1); testlib.assertEquals(errors[1].eventName, 'eventloop_test_protected'); testlib.assertTrue(string.find(tostring(errors[1].err), 'handler boom', 1, true) ~= nil); testlib.assertTrue(siblingCalled); testlib.assertTrue(laterCalled); end); testlib.test('protected timeout dispatch reports errors and keeps the loop alive', function() local errors = {}; local events = createEventLoop({ onError = function(eventName, err) errors[#errors + 1] = { eventName = eventName, err = err }; end, }); local laterCalled = false; events.setTimeout(function() error('timeout boom', 0); end, 0); events.setTimeout(function() laterCalled = true; events.stopLoop(); end, 0); events.runLoop(); testlib.assertEquals(#errors, 1); testlib.assertEquals(errors[1].eventName, 'timer'); testlib.assertTrue(string.find(tostring(errors[1].err), 'timeout boom', 1, true) ~= nil); testlib.assertTrue(laterCalled); end); testlib.test('timeout handler can stop a loop with persistent handlers', function() local events = createEventLoop(); events.register('eventloop_test_never_fired', function() end); events.setTimeout(function() events.stopLoop(); end, 0); events.runLoop(); testlib.assertTrue(not events.isRunningLoop()); end); testlib.test('empty loop returns and runs onStop', function() local events = createEventLoop(); local stopped = false; events.onStop(function() stopped = true; end); events.runLoop(); testlib.assertTrue(stopped); testlib.assertTrue(not events.isRunningLoop()); end); testlib.test('error contracts are enforced', function() local events = createEventLoop(); local function handler() end events.register('eventloop_test_errors', handler); testlib.assertErrors(function() events.register('eventloop_test_errors', handler); end, 'handler already registered'); testlib.assertErrors(function() events.stopLoop(); end, 'loop is already stopped'); testlib.assertErrors(function() events.register(1, handler); end, 'string expected'); testlib.assertErrors(function() events.register('eventloop_test_errors_2', 'not a function'); end, 'function expected'); end); testlib.run();