diff --git a/test/fixtures/runtime-default-boot.ts b/test/fixtures/runtime-default-boot.ts new file mode 100644 index 0000000..913c589 --- /dev/null +++ b/test/fixtures/runtime-default-boot.ts @@ -0,0 +1,7 @@ +import { runtime } from 'moroutine'; +import { double } from './math.ts'; + +// No registerRuntime() — first dispatch boots the DEFAULT definition. +const viaRun = await runtime.run(double(4)); +console.log('SIZE ' + runtime.workers.length); +console.log('DONE ' + viaRun); diff --git a/test/fixtures/runtime-natural-exit.ts b/test/fixtures/runtime-natural-exit.ts new file mode 100644 index 0000000..074703b --- /dev/null +++ b/test/fixtures/runtime-natural-exit.ts @@ -0,0 +1,8 @@ +import { runtime, registerRuntime } from 'moroutine'; +import { tinyRuntime } from './runtime-def.ts'; +import { busy } from './load-balancing.ts'; + +registerRuntime(tinyRuntime); +const r = await runtime.run(busy(20)); +console.log('DONE ' + r); +// No shutdown() call — the unref'd runtime must let the process exit here. diff --git a/test/fixtures/runtime-shutdown.ts b/test/fixtures/runtime-shutdown.ts new file mode 100644 index 0000000..04881b4 --- /dev/null +++ b/test/fixtures/runtime-shutdown.ts @@ -0,0 +1,20 @@ +import { runtime, registerRuntime } from 'moroutine'; +import { tinyRuntime } from './runtime-def.ts'; +import { double } from './math.ts'; + +registerRuntime(tinyRuntime); +await runtime.run(double(1)); + +let signalFired = false; +runtime.signal.addEventListener('abort', () => { + signalFired = true; +}); +await runtime.shutdown(); +console.log('SIGNAL ' + signalFired); + +try { + await runtime.run(double(2)); + console.log('POST-SHUTDOWN-DISPATCH no-throw'); +} catch (err) { + console.log('POST-SHUTDOWN-DISPATCH threw'); +} diff --git a/test/runtime-lifecycle.test.ts b/test/runtime-lifecycle.test.ts new file mode 100644 index 0000000..4a20002 --- /dev/null +++ b/test/runtime-lifecycle.test.ts @@ -0,0 +1,35 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import { fileURLToPath } from 'node:url'; +import { join } from 'node:path'; +import { availableParallelism } from 'node:os'; + +const exec = promisify(execFile); +const fixturesDir = join(fileURLToPath(import.meta.url), '..', 'fixtures'); + +describe('global runtime lifecycle', () => { + it('first dispatch lazily boots the default definition', async () => { + const { stdout } = await exec(process.execPath, ['--no-warnings', join(fixturesDir, 'runtime-default-boot.ts')], { + timeout: 20000, + }); + assert.ok(stdout.includes(`SIZE ${availableParallelism()}`)); + assert.ok(stdout.includes('DONE 8')); + }); + + it('process exits naturally without shutdown()', async () => { + const { stdout } = await exec(process.execPath, ['--no-warnings', join(fixturesDir, 'runtime-natural-exit.ts')], { + timeout: 15000, + }); + assert.match(stdout, /DONE done/); + }); + + it('shutdown() fires signal, drains, and locks out further dispatch', async () => { + const { stdout } = await exec(process.execPath, ['--no-warnings', join(fixturesDir, 'runtime-shutdown.ts')], { + timeout: 15000, + }); + assert.ok(stdout.includes('SIGNAL true')); + assert.ok(stdout.includes('POST-SHUTDOWN-DISPATCH threw')); + }); +});