-- Eventloop behavior tests for the CraftOS-PC harness. -- Invoked via `craftos --headless --script tests/eventloop.lua` from `just test`. local createEventLoop = require('/apis/eventloop'); local args = { ... }; local verbose = false; local tests = {}; for _, arg in ipairs(args) do if arg == '--verbose' then verbose = true; end end local function test(name, fn) tests[#tests + 1] = { name = name, fn = fn }; end local function fail(message) error(message, 2); end local function assertEquals(actual, expected, message) if actual ~= expected then fail((message or 'assertEquals failed') .. ': expected ' .. tostring(expected) .. ', got ' .. tostring(actual)); end end local function assertTrue(value, message) if not value then fail(message or 'assertTrue failed'); end end local function assertErrors(fn, expected) local ok, err = pcall(fn); if ok then fail('expected error'); end if expected and not string.find(tostring(err), expected, 1, true) then fail('expected error containing ' .. expected .. ', got ' .. tostring(err)); end end test('register dispatches queued event args', function() local events = createEventLoop(); local called = 0; events.register('eventloop_test_basic', function(a, b) called = called + 1; assertEquals(a, 'first'); assertEquals(b, 42); events.stopLoop(); end); os.queueEvent('eventloop_test_basic', 'first', 42); events.runLoop(); assertEquals(called, 1); assertTrue(not events.isRunningLoop()); end); 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(); assertEquals(stoppedHandlerCalls, 1); assertEquals(observerCalls, 2); end); 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(); assertEquals(called, 0); end); 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(); assertEquals(called, 1); end); 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(); assertEquals(order, 'event>timeout'); end); 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(); assertEquals(called, 0); end); 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(); assertEquals(order, 'start>timeout>stop'); end); test('empty loop returns and runs onStop', function() local events = createEventLoop(); local stopped = false; events.onStop(function() stopped = true; end); events.runLoop(); assertTrue(stopped); assertTrue(not events.isRunningLoop()); end); test('error contracts are enforced', function() local events = createEventLoop(); local function handler() end events.register('eventloop_test_errors', handler); assertErrors(function() events.register('eventloop_test_errors', handler); end, 'handler already registered'); assertErrors(function() events.stopLoop(); end, 'loop is already stopped'); assertErrors(function() events.register(1, handler); end, 'string expected'); assertErrors(function() events.register('eventloop_test_errors_2', 'not a function'); end, 'function expected'); end); for _, t in ipairs(tests) do if verbose then print('RUN ' .. t.name); end local ok, err = pcall(t.fn); if not ok then print('FAIL ' .. t.name .. ': ' .. tostring(err)); os.shutdown(); end end print('__READY__'); os.shutdown();