UNPKG

spica

Version:

Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.

578 lines (546 loc) 21.4 kB
import { Supervisor } from './supervisor'; import { Coroutine } from './coroutine'; import { never } from './promise'; import { clock } from './chrono'; import { wait } from './timer'; describe('Unit: lib/supervisor', function () { describe('Supervisor', function () { before(() => { assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); }); afterEach(() => { assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); }); it('extend', function (done) { class TestSupervisor extends Supervisor<string, number, number, number> { } assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); const sv1 = new TestSupervisor(); assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); const sv2 = new class TestSupervisor extends Supervisor<string, number, number, number> { }(); assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); assert(class extends TestSupervisor { }.status.instances === 0); assert(class extends TestSupervisor { }.status.processes === 0); const process: Supervisor.Process<number, number, number> = { init: (state) => state, main: (n, s) => [n, ++s], exit: () => undefined }; const kill = sv1.register('', process, 0); assert.deepStrictEqual(sv1.refs(), [ ['', process, 0, kill] ]); assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 1); sv1.kill(''); assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); sv1.terminate(); assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); sv2.terminate(); assert(Supervisor.status.instances === 0); assert(Supervisor.status.processes === 0); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); done(); }); it('refs', function (done) { class TestSupervisor extends Supervisor<string, number, number, number> { } assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); const sv = new TestSupervisor(); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); const process: Supervisor.Process<number, number, number> = { init: (state) => state, main: (n, s) => [n, ++s], exit: () => undefined }; const kill = sv.register('', process, 0); assert.deepStrictEqual(sv.refs(), [ ['', process, 0, kill] ]); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 1); sv.kill(''); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); sv.terminate(); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); done(); }); it('lifecycle', function (done) { let cnt = 0; class TestSupervisor extends Supervisor<string, number, number, number> { } assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); const sv = new TestSupervisor({ name: '', destructor: reason => { assert(reason === undefined); assert(cnt === 13 && ++cnt); assert.throws(() => sv.register('', () => [0, 0], 0)); assert.throws(() => sv.cast('', 0)); assert.throws(() => sv.call('', 0, () => undefined)); assert(sv.terminate() === false); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); done(); } }); assert(sv.name === ''); sv.events.init .monitor([], ([name, process, state]) => { assert(TestSupervisor.status.processes === 1); assert(cnt === 1 && ++cnt); assert(name === ''); assert(process.init instanceof Function); assert(process.main instanceof Function); assert(process.exit instanceof Function); assert(state === 0); }); sv.events.loss .monitor([], ([name, param]) => { assert(TestSupervisor.status.processes === 0); assert(name === ''); switch (cnt) { case 11: assert(param === 4 && ++cnt); break; case 14: assert(param === 0 && ++cnt); break; default: assert(false); } }); sv.events.exit .monitor([], ([name, process, state, reason]) => { assert(TestSupervisor.status.processes === 0); assert(cnt === 9 && ++cnt); assert(name === ''); assert(process.init instanceof Function); assert(process.main instanceof Function); assert(process.exit instanceof Function); assert(state === 2); assert(reason instanceof Error); }); sv.register('', { init(state) { assert(TestSupervisor.status.processes === 1); assert(cnt === 2 && ++cnt); assert(state === 0); return state; }, main(n, state) { assert(TestSupervisor.status.processes === 1); switch (cnt) { case 3: assert(n === 2 && ++cnt); break; case 5: assert(n === 1 && ++cnt); break; case 7: assert(n === 3 && ++cnt); throw new Error(); default: throw new Error(); } return [-n, ++state]; }, exit(reason, state) { assert(TestSupervisor.status.processes === 0); assert(cnt === 8 && ++cnt); assert(reason instanceof Error); assert(state === 2); } }, 0); assert(cnt === 0 && ++cnt); sv.call('', 1, (_, r) => void assert(TestSupervisor.status.processes === 1) || void assert(r === -1) || assert(cnt === 6 && ++cnt)); assert(sv.cast('', 2)); sv.call('', 3, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 10 && ++cnt)); sv.call('', 4, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || void assert(cnt === 12 && ++cnt) || sv.terminate(), 100); assert(cnt === 4 && ++cnt); }); it('register', function (done) { const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.events.exit.once([''], done); sv.register('', (_, s) => [s, s], 1); assert.throws(() => sv.register('', (_, s) => [s, s], 2)); done(); }); it('validation of returned values', function (done) { const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }(); sv.register('', () => new Promise<any>(resolve => void resolve(undefined)), 0); sv.call('', 0, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || done()); }); it('state', function (done) { const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.register('', (n, s) => Promise.resolve([n + s, ++s]), 0); sv.call('', 1, (_, r) => assert(r === 1)); sv.call('', 2, (_, r) => void assert(r === 3) || done(), 100); }); it('exit', function (done) { const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); let inits = 0; let exits = 0; sv.register('', { init: () => ++inits, main: (n, s) => [n + s, ++s], exit: () => ++exits }, 0); assert(inits === exits); assert(inits === 0); sv.kill('', 0); assert(inits === exits); assert(inits === 0); sv.register('', { init: () => ++inits, main: (n, s) => [n + s, ++s], exit: () => ++exits }, 0); sv.cast('', 1); assert(inits === 1); assert(exits === 0); sv.kill('', 0); assert(inits === exits); assert(inits === 1); done(); }); it('generator', async function () { const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); // This type annotation should be removed after #36053 is fixed. sv.register('', function* (state): Generator<number, number, number> { assert(state === 0); assert(1 === (yield 0)); assert(2 === (yield 1)); return 2; }, 0); assert(await sv.call('', 1) === 1); await wait(100); assert(sv.refs('').length === 1); await wait(100); assert(await sv.call('', 2) === 2); await wait(100); assert(sv.kill('') === false); // This type annotation should be removed after #36053 is fixed. sv.register('', function* (): Generator<number, number, number> { throw 1; }, 0); sv.cast('', 0); assert(sv.refs('').length === 0); assert(sv.kill('') === false); }); it('async generator', async function () { const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.register('', async function* (state) { assert(state === 0); assert(1 === (yield 0)); assert(2 === (yield 1)); return 2; }, 0); assert(await sv.call('', 1) === 1); await wait(100); assert(sv.refs('').length === 1); await wait(100); assert(await sv.call('', 2) === 2); await wait(100); assert(sv.kill('') === false); sv.register('', async function* () { throw 1; }, 0); sv.cast('', 0); assert(sv.refs('').length === 1); await wait(100); assert(sv.kill('') === false); }); it('coroutine', async function () { const sv = new class TestSupervisor extends Supervisor<string, number, number> { }({ }); sv.register('', new Coroutine<number, number, number>(async function* () { assert(1 === (yield 0)); assert(3 === (yield 2)); return 4; }, { capacity: 0 })); assert(await sv.call('', 1) === 2); await wait(100); assert(sv.refs('').length === 1); await wait(100); assert(await sv.call('', 3) === 4); await wait(100); assert(sv.kill('') === false); assert(sv.refs('').length === 0); const co = new Coroutine<number, number, number>(async function* () { return never; }); sv.register('', co); await wait(100); assert(sv.kill('') === true); assert(await co.catch(() => 0) === 0); sv.register('', new Coroutine<number, number, number>(async function* () { throw 1; })); assert(sv.refs('').length === 1); await wait(100); assert(sv.refs('').length === 0); assert(sv.kill('') === false); sv.register('', new Coroutine<number, number, number>(async function* () { throw 1; })); assert(sv.refs('').length === 1); await wait(100); assert(sv.kill('') === false); const ssv = new class TestSupervisor extends Supervisor<''> { }(); assert(ssv instanceof Coroutine); ssv.register('', sv); assert.doesNotThrow(() => ssv[Coroutine.port]); assert.throws(() => ssv[Coroutine.port].recv()); assert.throws(() => ssv[Coroutine.port].send()); assert.throws(() => ssv[Coroutine.port].connect()); assert(ssv.refs().length === 1); ssv.terminate(); assert(sv.kill('') === false); assert(await sv === undefined); assert(await ssv === undefined); }); it('timeout of messaging', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ timeout: 0 }); sv.events.loss.once([''], ([, n]) => { assert(n === 2); assert(cnt === 0 && ++cnt); sv.events.loss.once([''], ([, n]) => { assert(n === 1); assert(cnt === 2 && ++cnt); }); }); sv.events.loss.once([undefined], ([, n]) => { assert(n === 3); assert(cnt === 4 && ++cnt); done(); }); sv.call('', 1, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 3 && ++cnt), 100); sv.call('', 2, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 1 && ++cnt)); sv.call(() => [], 3, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 5 && ++cnt), 100); }); it('timeout of processing', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ timeout: 100 }); sv.events.exit.once([''], ([, , s, r]) => { assert(s === 0); assert(r instanceof Error); assert(cnt === 0 && ++cnt); done(); }); sv.register('', () => never, 0); sv.call('', 1, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 1 && ++cnt)); }); it('overflow', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ capacity: 1 }); sv.events.loss.once([''], ([n, p]) => { assert(n === ''); assert(p === 1); assert(cnt === 0 && ++cnt); }); sv.register('', () => [0, 0], 0); sv.call('', 1, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 1 && ++cnt)); sv.call('', 2, (e, r) => void assert(r === 0) || void assert(e === undefined) || void assert(cnt === 2 && ++cnt) || done(), 100); }); it('async', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.register('', () => [++cnt, 0], 0); sv.call('', 0, () => void assert(cnt === 1) || done()); assert(cnt === 0); }); it('await', async function () { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.register('', n => n ? [++cnt, 0] : Promise.reject(undefined), 0); assert(await sv.call('', 1) === 1); try { await sv.call('', 0); throw 0; } catch (reason) { assert(reason instanceof Error); } }); it('block', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.events.loss.on([''], ([, param]) => assert(cnt === 0 && param === 2 && ++cnt)); sv.register('', n => void assert(!sv.cast('', 2)) || void assert(n === 1) && void assert(cnt === 1 && ++cnt) || void clock.next(() => done()) || [0, 0], 0); assert(sv.cast('', 1)); }); it('block async', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.register('', n => new Promise(resolve => void setTimeout(() => void resolve([n, 0]), 100)), 0); sv.events.loss.on([''], ([, param]) => assert(cnt === 0 && param === 2 && ++cnt)); sv.call('', 1, (_, r) => void assert(r === 1) || assert(cnt === 2 && ++cnt), 1000); sv.call('', 2, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 1 && ++cnt), 0); sv.call('', 3, (_, r) => void assert(r === 3) || void assert(cnt === 3 && ++cnt) || done(), Infinity); }); it('scheduler', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ scheduler: requestAnimationFrame }); sv.register('', () => [++cnt, 0], 0); sv.call('', 0, () => void assert(cnt === 1) || done()); assert(cnt === 0); }); it('pool', function (done) { let cnt = 0; const sv = new class TestSupervisor extends Supervisor<string, number, number, number> { }({ }); sv.register('0', (_, s) => [s + ++cnt, 0], 0); sv.register('1', (_, s) => [s + ++cnt, 0], 1); sv.register('2', (_, s) => [s + ++cnt, 0], 2); sv.call(() => [], 0, (e, r) => void assert(r === undefined) || void assert(e instanceof Error) || assert(cnt === 0 && ++cnt), 0); sv.call(ns => ns, 0, (_, r) => assert(r === 2)); sv.call(ns => ns, 0, (_, r) => assert(r === 4)); sv.call(ns => ns, 0, (_, r) => assert(r === 6)); sv.call(ns => ns, 0, (_, r) => void assert(r === 5) || done()); }); it('kill', function (done) { class TestSupervisor extends Supervisor<string, number, number, number> { } assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); const sv = new TestSupervisor({}); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); void sv.register(' ', () => [0, 0], 0); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 1); assert(sv.kill('') === false); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 1); assert(sv.kill(' ') === true); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); assert(sv.terminate() === true); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); done(); }); it('clear', function (done) { class TestSupervisor extends Supervisor<string, number, number, number> { } assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); const sv = new TestSupervisor({}); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); void sv.register('1', () => [0, 0], 0); void sv.register('2', () => [0, 0], 0); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 2); sv.clear(); assert(TestSupervisor.status.instances === 1); assert(TestSupervisor.status.processes === 0); assert(sv.terminate() === true); sv.clear(); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); new TestSupervisor({}); new TestSupervisor({}); assert(TestSupervisor.status.instances === 2); assert(TestSupervisor.status.processes === 0); TestSupervisor.clear(); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); done(); }); it('terminate', function (done) { let cnt = 0; class TestSupervisor extends Supervisor<string, number, number, number> { } assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); const sv = new TestSupervisor({ destructor: reason => { assert(reason === undefined); assert.throws(() => sv.register('', () => [0, 0], 0)); assert.throws(() => sv.cast('', 0)); assert.throws(() => sv.call('', 0, () => undefined)); assert(sv.terminate() === false); assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); ++cnt; } }); const kill = sv.register('', () => [0, 0], 0); assert(sv.terminate() === true); assert(kill() === false); assert(sv.terminate() === false); assert(cnt === 1); try { sv.register('', () => [0, 0], 0); throw 0; } catch (reason) { assert(reason instanceof Error); } try { sv.cast('', 0); throw 0; } catch (reason) { assert(reason instanceof Error); } try { sv.call('', 0, () => 0); throw 0; } catch (reason) { assert(reason instanceof Error); } assert(TestSupervisor.status.instances === 0); assert(TestSupervisor.status.processes === 0); done(); }); }); });