spica
Version:
Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.
578 lines (546 loc) • 21.4 kB
text/typescript
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();
});
});
});