irc-block-tracker
Version:
A block tracker for the Ir blockchain. Keeps track of the latest block.
171 lines (141 loc) • 6.22 kB
JavaScript
const test = require('tape');
const noop = () => {};
const timeout = (duration) => new Promise(resolve => setTimeout(resolve, duration));
module.exports = (test, testLabel, BaseBlockTracker) => {
test(`${testLabel} - autostart`, (t) => {
const blockTracker = new BaseBlockTracker();
t.equal(blockTracker.isRunning(), false, 'block tracker is stopped after creation');
// check non-starts
blockTracker.once('hello', () => t.fail('should never be called'));
t.equal(blockTracker.isRunning(), false, 'block tracker is not started by unrelated event');
// check start
blockTracker.once('latest', noop);
t.equal(blockTracker.isRunning(), true, 'block tracker is started by "latest" event');
// check stop
blockTracker.removeListener('latest', noop);
t.equal(blockTracker.isRunning(), false, 'block tracker stops after all handlers are removed');
// cleanup
blockTracker.removeAllListeners();
t.end();
});
test(`${testLabel} - "_start" and "_stop" methods`, (t) => {
let start = 0;
let end = 0;
// custom subclass to track calls to _start/_end
class TestBlockTracker extends BaseBlockTracker {
_start() {
start++;
}
_end() {
end++;
}
}
const blockTracker = new TestBlockTracker();
// basic start stop check
t.equal(start, 0, 'start is not called during instantiation');
t.equal(end, 0, 'end is not called during instantiation');
blockTracker.on('latest', noop);
t.equal(start, 1, 'start is called once with new handler');
t.equal(end, 0, 'end is not called on new handler');
blockTracker.on('latest', noop);
t.equal(start, 1, 'start is not called again with 2nd handler');
t.equal(end, 0, 'end is not called on 2nd handler');
blockTracker.on('sync', noop);
t.equal(start, 1, 'start is not called again with 3rd different handler');
t.equal(end, 0, 'end is not called on 3rd different handler');
blockTracker.removeAllListeners();
t.equal(start, 1, 'start is not called again after removing handler');
t.equal(end, 1, 'end is called after all handlers removed');
// multiple separate removes calls end only once
blockTracker.once('latest', noop);
t.equal(start, 2, 'start is called again on new handler');
t.equal(end, 1, 'end is not called again');
blockTracker.once('latest', noop);
blockTracker.once('latest', noop);
blockTracker.emit('latest');
t.equal(start, 2, 'start is called again after triggering "once" handlers');
t.equal(end, 2, 'end is called only once after clearing "once" hanlders');
// cleanup
blockTracker.removeAllListeners();
t.end();
});
test(`${testLabel} - block staleness`, async (t) => {
let blockFetchs = 0;
// custom subclass to track calls to _start/_end
class TestBlockTracker extends BaseBlockTracker {
async _fetchLatestBlock() {
blockFetchs++;
// dummy block with unique hash
return {hash: blockFetchs};
}
async _start() {
const latestBlock = await this._fetchLatestBlock();
this._newPotentialLatest(latestBlock);
}
}
const blockResetDuration = 100;
const blockTracker = new TestBlockTracker({
blockResetDuration,
});
try {
t.equal(blockTracker.isRunning(), false, 'block tracker is stopped after creation');
t.notOk(blockTracker.getCurrentBlock(), 'block tracker has no current block');
t.equal(blockFetchs, 0, 'block-tracker has not fetched any blocks');
const firstBlock = await blockTracker.getLatestBlock();
t.ok(firstBlock, 'blockTracker.getLatestBlock returned a block');
t.equal(blockFetchs, 1, 'block-tracker has fetched one block');
t.equal(blockTracker.isRunning(), false, 'block tracker is still stopped');
t.ok(blockTracker.getCurrentBlock(), 'block tracker has a current block');
await timeout(blockResetDuration);
t.equal(blockFetchs, 1, 'block-tracker still has fetched only one block');
t.equal(blockTracker.isRunning(), false, 'block tracker is still stopped');
t.notOk(blockTracker.getCurrentBlock(), 'block tracker has no current block');
const secondBlock = await blockTracker.getLatestBlock();
t.ok(secondBlock, 'blockTracker.getLatestBlock returned a block');
t.equal(blockFetchs, 2, 'block-tracker has fetched a 2nd block');
t.equal(blockTracker.isRunning(), false, 'block tracker is still stopped');
t.ok(blockTracker.getCurrentBlock(), 'block tracker has a current block');
} catch (err) {
console.error(err);
t.ifError(err);
}
// cleanup
blockTracker.removeAllListeners();
t.end();
});
test(`${testLabel} - multiple getLatestBlock when off`, async (t) => {
let blockFetchs = 0;
// custom subclass to track calls to _start/_end
class TestBlockTracker extends BaseBlockTracker {
async _fetchLatestBlock() {
blockFetchs++;
// dummy block with unique hash
return {hash: blockFetchs};
}
async _start() {
const latestBlock = await this._fetchLatestBlock();
this._newPotentialLatest(latestBlock);
}
}
const blockResetDuration = 100;
const blockTracker = new TestBlockTracker({
blockResetDuration,
});
t.equal(blockTracker.isRunning(), false, 'block tracker is stopped after creation');
t.notOk(blockTracker.getCurrentBlock(), 'block tracker has no current block');
t.equal(blockFetchs, 0, 'block-tracker has not fetched any blocks');
let block1, block2;
await Promise.all([
(async () => { block1 = await blockTracker.getLatestBlock(); })(),
(async () => { block2 = await blockTracker.getLatestBlock(); })(),
]);
t.ok(block1, 'blockTracker.getLatestBlock returned a block');
t.ok(block2, 'blockTracker.getLatestBlock returned a block');
t.equal(block1, block2, 'blocks are the same obj');
t.equal(blockFetchs, 1, 'block-tracker has fetched only one block');
t.equal(blockTracker.isRunning(), false, 'block tracker is still stopped');
// cleanup
blockTracker.removeAllListeners();
t.end();
});
};