node-toobusy
Version:
Don't fall over when your Node.JS server is too busy. Now without native dependencies!
236 lines (205 loc) • 6.41 kB
JavaScript
;
var should = require('should');
var toobusy = require('./');
function tightWork(duration) {
var start = Date.now();
while ((Date.now() - start) < duration) {
for (var i = 0; i < 1e5;) i++;
}
}
/*global describe, it, beforeEach, afterEach */
describe('the library', function() {
it('should export a couple functions', function() {
(toobusy).should.be.a('function');
(toobusy.maxLag).should.be.a('function');
(toobusy.shutdown).should.be.a('function');
(toobusy.interval).should.be.a('function');
(toobusy.shutdown).should.be.a('function');
(toobusy).should.not.have.property('start');
});
it('should start automatically', function() {
(toobusy.started()).should.equal(true);
});
});
describe('maxLag', function() {
it('should default to 70', function() {
(toobusy.maxLag()).should.equal(70);
});
it('should throw an exception for non-numbers', function() {
(function() { toobusy.maxLag('derp'); }).should.throw(/must be a number/);
});
it('should throw an exception for values < 10', function() {
(function() { toobusy.maxLag(9); }).should.throw(/should be greater than 10/);
});
it('should be configurable', function() {
(toobusy.maxLag(50)).should.equal(50);
(toobusy.maxLag(10)).should.equal(10);
(toobusy.maxLag()).should.equal(10);
});
});
describe('interval', function() {
it('should default to 500', function() {
(toobusy.interval()).should.equal(500);
});
it('should throw an exception for values < 16', function() {
(function() { toobusy.interval(15); }).should.throw(/Interval/);
});
it('should be configurable', function() {
(toobusy.interval(250)).should.equal(250);
(toobusy.interval(300)).should.equal(300);
(toobusy.interval()).should.equal(300);
});
});
describe('toobusy()', function() {
// Set lower thresholds for each of these tests.
// Resetting the interval() also resets the internal lag counter, which
// is nice for making these tests independent of each other.
beforeEach(function() {
toobusy.maxLag(10);
toobusy.interval(250);
});
afterEach(function() {
toobusy.maxLag(70);
toobusy.interval(500);
});
it('should return true after a little load', function(done) {
function load() {
if (toobusy()) return done();
tightWork(100);
setTimeout(load, 0);
}
load();
});
it('should return a lag value after a little load', function(done) {
function load() {
if (toobusy()) {
var lag = toobusy.lag();
should.exist(lag);
lag.should.be.above(1);
return done();
}
tightWork(100);
setTimeout(load, 0);
}
load();
});
describe('lag events', function () {
//Sometimes the default 2s timeout is hit on this suite, raise to 10s.
this.timeout(10 * 1000);
it('should not emit lag events if the lag is less than the configured threshold',
testLagEvent(100, 50, false));
it('should emit lag events if the lag is greater than the configured threshold',
testLagEvent(50, 100, true));
it('should emit lag events if lag occurs and no threshold is specified',
testLagEvent(undefined, 500, true));
function testLagEvent(threshold, work, expectFire) {
return function (done) {
var calledDone = false;
toobusy.onLag(function (lag) {
if (calledDone) {
return;
}
if (!expectFire) {
calledDone = true;
done(new Error('lag event fired unexpectedly'));
return;
}
should.exist(lag);
lag.should.be.above(threshold || 0);
calledDone = true;
done();
}, threshold);
if (!expectFire) {
setTimeout(function () {
if (!calledDone) {
calledDone = true;
done();
}
}, work + threshold);
}
tightWork(work);
}
}
});
});
describe('smoothingFactor', function() {
//Sometimes the default 2s timeout is hit on this suite, raise to 10s.
this.timeout(10 * 1000);
beforeEach(function() {
toobusy.reset();
toobusy.maxLag(10);
toobusy.interval(250);
});
afterEach(function() {
toobusy.maxLag(70);
toobusy.interval(500);
});
setupSmoothingFactorTests({
function: toobusy.smoothingFactorOnRise,
suiteName: 'on rise',
default: 1/3
});
setupSmoothingFactorTests({
function: toobusy.smoothingFactorOnFall,
suiteName: 'on fall',
default: 1 - 1/3
});
it('should allow no dampening', function (done) {
var cycles_to_toobusy = 0;
toobusy.smoothingFactorOnRise(1); // no dampening
toobusy.smoothingFactorOnFall(1); // no dampening
function load() {
if (toobusy()) {
(cycles_to_toobusy).should.equal(3);
return done();
}
cycles_to_toobusy++;
tightWork(100); // in 3 ticks, will overshoot by ~50ms, above 2*10ms
setImmediate(load);
}
load();
});
it('should respect larger dampening factors', function (done) {
var cycles_to_toobusy = 0;
toobusy.smoothingFactorOnRise(0.05);
function load() {
if (toobusy()) {
(cycles_to_toobusy).should.be.above(3);
return done();
}
cycles_to_toobusy++;
tightWork(100);
setImmediate(load);
}
load();
});
});
function setupSmoothingFactorTests(options) {
var smoothingFunc = options.function;
describe(options.suiteName, function() {
it('should default to ' + options.default, function(done) {
(smoothingFunc()).should.equal(options.default);
done();
});
it('should throw an exception for invalid values', function(done) {
(function() { smoothingFunc(0); }).should.throw;
(function() { smoothingFunc(2); }).should.throw;
(function() { smoothingFunc(-1); }).should.throw;
(function() { smoothingFunc(1); }).should.not.throw;
done();
});
it('should be configurable', function(done) {
(smoothingFunc(0.9)).should.equal(0.9);
(smoothingFunc(0.1)).should.equal(0.1);
(smoothingFunc()).should.equal(0.1);
done();
});
});
};
describe('started', function() {
it('should return false after shutdown', function(done) {
toobusy.shutdown();
(toobusy.started()).should.equal(false);
done();
});
});