import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { workers } from 'moroutine'; import { fail, failType, failCause } from './fixtures/math.ts'; import { failAfterType } from './fixtures/stream-gen.ts'; describe('error handling', () => { it('rejects with error from dedicated worker', async () => { await assert.rejects(async () => fail('boom').then((x) => x), { message: 'boom', }); }); it('rejects with error from pool worker', async () => { await using run = workers(1); await assert.rejects(() => run(fail('pool boom')), { message: 'pool boom', }); }); it('preserves stack trace pointing to worker source via cause', async () => { await using run = workers(1); try { await run(fail('stack check')); assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof Error); const cause = err.cause as Error; assert.ok(cause instanceof Error); assert.match(cause.stack!, /fixtures\/math\.ts/); } }); it('preserves built-in error subclass identity', async () => { await using run = workers(1); try { await run(failType('type check')); assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof TypeError); assert.equal((err as Error).message, 'type check'); } }); it('preserves error cause (nested through wrapper)', async () => { await using run = workers(1); try { await run(failCause('with cause')); assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof Error); // outer wrapper has the worker error as cause; worker error has RangeError as its cause const workerErr = (err as Error).cause as Error; assert.ok(workerErr instanceof Error); assert.ok(workerErr.cause instanceof RangeError); assert.equal((workerErr.cause as Error).message, 'root cause'); } }); it('preserves error details on dedicated worker', async () => { try { await failType('dedicated type'); assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof TypeError); const cause = (err as Error).cause as Error; assert.match(cause.stack!, /fixtures\/math\.ts/); } }); it('main-thread stack includes run() caller', async () => { await using run = workers(1); async function someCaller() { await run(fail('trace check')); } try { await someCaller(); assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof Error); assert.match((err as Error).stack!, /someCaller/); } }); it('main-thread stack includes exec() caller', async () => { await using run = workers(1); async function someCaller() { await run.workers[0].exec(fail('exec trace check')); } try { await someCaller(); assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof Error); assert.match((err as Error).stack!, /someCaller/); } }); it('main-thread stack includes await-task caller on dedicated worker', async () => { async function someCaller() { await fail('dedicated trace check'); } try { await someCaller(); assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof Error); assert.match((err as Error).stack!, /someCaller/); } }); it('preserves error details on streaming task', async () => { await using run = workers(1); try { for await (const _ of run(failAfterType(2))) { // consume yields until error } assert.fail('should have thrown'); } catch (err) { assert.ok(err instanceof TypeError); assert.equal((err as Error).message, 'stream type error'); assert.match((err as Error).stack!, /fixtures\/stream-gen\.ts/); } }); });