const path = require('path');
const { startRelay, stopRelay, stopAll } = require('./helpers/relay');
const { setupPanic, teardownPanic } = require('./helpers/setup');
const binPath = path.resolve(__dirname, '../../target/debug/beam');
const config = {
panicPort: 8765,
relayPort: 9100,
soul: 'lattest',
target: 'target',
floodSeconds: 15,
latencyBoundMs: 10000,
};
const { clients, alice, bob } = setupPanic({ numClients: 2, panicPort: config.panicPort });
describe('13. Latency: reader receives target during sustained write flood', function () {
this.timeout(120000);
it('Two clients connected to panic-server', function () {
return clients.atLeast(2);
});
it('Relay starts', function () {
this.timeout(10000);
startRelay({ binPath, port: config.relayPort });
return new Promise((resolve) => setTimeout(resolve, 2000));
});
it('Flooder floods random puts and re-puts target for ' + config.floodSeconds + 's', function () {
this.timeout(config.floodSeconds * 1000 + 20000);
return alice.run(function (test) {
test.async();
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:' + test.props.relayPort);
const soul = test.props.soul;
const target = test.props.target;
const floodSeconds = test.props.floodSeconds;
ws.on('open', () => {
ws.send(JSON.stringify({ get: { '#': soul }, '#': 'latfsub' }));
const rand = (n) => Math.floor(Math.random() * n);
const putOne = (key, val, id) => {
ws.send(JSON.stringify({
'#': id,
put: {
[soul]: {
_: { '#': soul, '>': { [key]: Date.now() } },
[key]: val,
},
},
}));
};
putOne(target, 'hello world', 'lattgt0');
let t = 1;
const tgt = setInterval(() => putOne(target, 'hello world', 'lattgt' + t++), 1000);
const flood = setInterval(() => {
for (let b = 0; b < 10; b++) {
putOne(
'r' + rand(1e6).toString(36) + rand(100).toString(36),
rand(1e6).toString(36),
'latf' + rand(1e9).toString(36)
);
}
}, 50);
setTimeout(() => {
clearInterval(tgt);
clearInterval(flood);
ws.close();
test.done();
}, floodSeconds * 1000);
});
ws.on('error', () => { });
}, { relayPort: config.relayPort, soul: config.soul, target: config.target, floodSeconds: config.floodSeconds });
});
it('Reader joins mid-flood and receives target within bound', function () {
this.timeout(60000);
return bob.run(function (test) {
test.async();
const WebSocket = require('ws');
setTimeout(() => {
const t0 = Date.now();
const ws = new WebSocket('ws://localhost:' + test.props.relayPort);
const soul = test.props.soul;
const target = test.props.target;
const bound = test.props.bound;
let done = false;
ws.on('open', () => {
ws.send(JSON.stringify({
get: { '#': soul },
'#': 'latrsub' + Math.random().toString(36).slice(2),
}));
});
ws.on('message', (raw) => {
let msgs;
try { msgs = JSON.parse(raw.toString()); } catch (e) { return; }
if (!Array.isArray(msgs)) msgs = [msgs];
for (const m of msgs) {
if (m.put && m.put[soul] && m.put[soul][target] !== undefined && !done) {
done = true;
const latency = Date.now() - t0;
ws.close();
if (latency <= bound) {
test.done();
} else {
test.fail(`latency ${latency}ms > bound ${bound}ms`);
}
}
}
});
ws.on('error', (err) => { if (!done) { done = true; test.fail('WS error: ' + err.message); } });
setTimeout(() => {
if (!done) {
done = true;
ws.close();
test.fail('timeout — target never received within ' + bound + 'ms');
}
}, bound + 2000);
}, 2000);
}, { relayPort: config.relayPort, soul: config.soul, target: config.target, bound: config.latencyBoundMs });
});
after(function () {
teardownPanic();
});
});