You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2882 lines
81 KiB
2882 lines
81 KiB
/*global describe: false, |
|
it: false, |
|
fsq: false, |
|
expect: false, |
|
msg_dir: false, |
|
fs: false, |
|
QlobberFSQ: false, |
|
fsq_dir: false, |
|
flags: false, |
|
check_empty: false, |
|
async: false, |
|
path: false, |
|
crypto: false, |
|
lsof: false, |
|
rimraf: false, |
|
beforeEach: false, |
|
afterEach: false, |
|
ignore_ebusy: false, |
|
retry_interval: false, |
|
util: false, |
|
single_supported: false, |
|
os: false */ |
|
/*jslint node: true, nomen: true, bitwise: true, todo: true */ |
|
"use strict"; |
|
|
|
function read_all(s, cb) |
|
{ |
|
var bufs = []; |
|
|
|
s.on('end', function () |
|
{ |
|
cb(Buffer.concat(bufs)); |
|
}); |
|
|
|
s.on('readable', function () |
|
{ |
|
while (true) |
|
{ |
|
var data = this.read(); |
|
if (data === null) { break; } |
|
bufs.push(data); |
|
} |
|
}); |
|
} |
|
|
|
describe('qlobber-fsq', function () |
|
{ |
|
this.timeout(60 * 1000); |
|
|
|
var orig_ftruncate = fs.ftruncate, |
|
orig_rename = fs.rename, |
|
orig_close = fs.close, |
|
orig_flock = fs.flock; |
|
|
|
function restore() |
|
{ |
|
fs.ftruncate = orig_ftruncate; |
|
fs.rename = orig_rename; |
|
fs.close = orig_close; |
|
fs.flock = orig_flock; |
|
} |
|
|
|
/*jslint unparam: true */ |
|
beforeEach(function () |
|
{ |
|
var busied_ftruncate = false, |
|
busied_rename = false, |
|
busied_close = false, |
|
busied_flock = false; |
|
|
|
fs.ftruncate = function (fd, size, cb) |
|
{ |
|
if (busied_ftruncate) |
|
{ |
|
busied_ftruncate = false; |
|
return orig_ftruncate.apply(this, arguments); |
|
} |
|
|
|
busied_ftruncate = true; |
|
cb({ code: 'EBUSY' }); |
|
}; |
|
|
|
fs.rename = function (src, dest, cb) |
|
{ |
|
if (busied_rename) |
|
{ |
|
busied_rename = false; |
|
return orig_rename.apply(this, arguments); |
|
} |
|
|
|
busied_rename = true; |
|
cb({ code: 'EBUSY' }); |
|
}; |
|
|
|
// fs.WriteStream calls fs.close when it ends so if we're not using |
|
// fs-ext then don't overwrite fs.close otherwise publish will error |
|
if (!single_supported) |
|
{ |
|
return; |
|
} |
|
|
|
fs.close = function (fd, cb) |
|
{ |
|
if (busied_close) |
|
{ |
|
busied_close = false; |
|
return orig_close.apply(this, arguments); |
|
} |
|
|
|
busied_close = true; |
|
cb({ code: 'EBUSY' }); |
|
}; |
|
|
|
fs.flock = function (fd, type, cb) |
|
{ |
|
if (busied_flock) |
|
{ |
|
busied_flock = false; |
|
return orig_flock.apply(this, arguments); |
|
} |
|
|
|
busied_flock = true; |
|
cb({ code: 'EBUSY' }); |
|
}; |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
afterEach(restore); |
|
|
|
it('should subscribe and publish to a simple topic', function (done) |
|
{ |
|
fsq.subscribe('foo', function (data, info) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(info.single).to.equal(false); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(new Buffer('foo').toString('hex') + '@', 0)).to.equal(0); |
|
expect(info.topic_path).to.equal(undefined); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
done(); |
|
}); |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should construct received data only once', function (done) |
|
{ |
|
var the_data = { foo: 0.435, bar: 'hello' }, |
|
called1 = false, |
|
called2 = false, |
|
received_data; |
|
|
|
fsq.subscribe('test', function (data, info, cb) |
|
{ |
|
expect(info.topic).to.equal('test'); |
|
expect(JSON.parse(data)).to.eql(the_data); |
|
|
|
if (received_data) |
|
{ |
|
expect(data === received_data).to.equal(true); |
|
} |
|
else |
|
{ |
|
received_data = data; |
|
} |
|
|
|
called1 = true; |
|
|
|
if (called1 && called2) |
|
{ |
|
cb(null, done); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
}); |
|
|
|
fsq.subscribe('test', function (data, info, cb) |
|
{ |
|
expect(info.topic).to.equal('test'); |
|
expect(JSON.parse(data)).to.eql(the_data); |
|
|
|
if (received_data) |
|
{ |
|
expect(data === received_data).to.equal(true); |
|
} |
|
else |
|
{ |
|
received_data = data; |
|
} |
|
|
|
called2 = true; |
|
|
|
if (called1 && called2) |
|
{ |
|
cb(null, done); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
}); |
|
|
|
fsq.publish('test', JSON.stringify(the_data), function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should support more than 10 subscribers', function (done) |
|
{ |
|
var the_data = { foo: 0.435, bar: 'hello' }, |
|
counter = 11, |
|
received_data, |
|
a = [], |
|
i; |
|
|
|
function subscribe(cb) |
|
{ |
|
fsq.subscribe('test', function (data, info, cb) |
|
{ |
|
expect(info.topic).to.equal('test'); |
|
expect(JSON.parse(data)).to.eql(the_data); |
|
|
|
if (received_data) |
|
{ |
|
expect(data === received_data).to.equal(true); |
|
} |
|
else |
|
{ |
|
received_data = data; |
|
} |
|
|
|
counter -= 1; |
|
|
|
if (counter === 0) |
|
{ |
|
cb(null, done); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
}, cb); |
|
} |
|
|
|
for (i = counter; i > 0; i -= 1) |
|
{ |
|
a.push(subscribe); |
|
} |
|
|
|
async.parallel(a, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
fsq.publish('test', JSON.stringify(the_data), function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
|
|
it('should support more than 10 subscribers with same handler', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
dedup: false |
|
}), |
|
the_data = { foo: 0.435, bar: 'hello' }, |
|
counter = 11, |
|
received_data, |
|
a = [], |
|
i; |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
function handler(data, info, cb) |
|
{ |
|
expect(info.topic).to.equal('test'); |
|
expect(JSON.parse(data)).to.eql(the_data); |
|
|
|
if (received_data) |
|
{ |
|
expect(data === received_data).to.equal(true); |
|
} |
|
else |
|
{ |
|
received_data = data; |
|
} |
|
|
|
counter -= 1; |
|
|
|
if (counter === 0) |
|
{ |
|
cb(null, function (err) |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
done(err); |
|
}); |
|
}); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
} |
|
|
|
function subscribe(cb) |
|
{ |
|
fsq2.subscribe('test', handler, cb); |
|
} |
|
|
|
for (i = counter; i > 0; i -= 1) |
|
{ |
|
a.push(subscribe); |
|
} |
|
|
|
fsq2.on('start', function () |
|
{ |
|
async.parallel(a, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
fsq2.publish('test', JSON.stringify(the_data), function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should subscribe to wildcards', function (done) |
|
{ |
|
var count = 0; |
|
|
|
function received() |
|
{ |
|
count += 1; |
|
if (count === 2) { done(); } |
|
} |
|
|
|
fsq.subscribe('*', function (data, info) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
received(); |
|
}); |
|
|
|
fsq.subscribe('#', function (data, info) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
received(); |
|
}); |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should only call each handler once', function (done) |
|
{ |
|
var handler = function (data, info) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
done(); |
|
}; |
|
|
|
fsq.subscribe('*', handler); |
|
fsq.subscribe('#', handler); |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should be able to disable handler dedup', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
dedup: false |
|
}), count_multi = 0, count_single = 0; |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
function handler(data, info, cb) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
|
|
if (info.single) |
|
{ |
|
count_single += 1; |
|
} |
|
else |
|
{ |
|
count_multi += 1; |
|
} |
|
|
|
if ((count_single === (single_supported ? 1 : 0)) && |
|
(count_multi === 2)) |
|
{ |
|
cb(null, function (err) |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
done(err); |
|
}); |
|
}); |
|
} |
|
else |
|
{ |
|
if ((count_single > 1) || (count_multi > 2)) |
|
{ |
|
throw new Error('called too many times'); |
|
} |
|
|
|
cb(); |
|
} |
|
} |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.subscribe('*', handler); |
|
fsq2.subscribe('#', handler); |
|
|
|
fsq2.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
fsq2.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should call all handlers on a topic for pubsub', function (done) |
|
{ |
|
var count = 0; |
|
|
|
function received() |
|
{ |
|
count += 1; |
|
if (count === 2) { done(); } |
|
} |
|
|
|
function handler(data, info) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
received(); |
|
} |
|
|
|
fsq.subscribe('foo', function () { handler.apply(this, arguments); }); |
|
fsq.subscribe('foo', function () { handler.apply(this, arguments); }); |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
it('should support a work queue', function (done) |
|
{ |
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(info.single).to.equal(true); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(new Buffer('foo').toString('hex') + '@', 0)).to.equal(0); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
|
|
fs.stat(info.path, function (err) |
|
{ |
|
expect(err).to.equal(null); |
|
|
|
fs.open(info.path, 'r+', function (err, fd) |
|
{ |
|
expect(err).to.equal(null); |
|
|
|
orig_flock(fd, 'exnb', function (err) |
|
{ |
|
expect(err.code).to.equal('EAGAIN'); |
|
|
|
orig_close(fd, function (err) |
|
{ |
|
expect(err).to.equal(null); |
|
|
|
cb(null, function () |
|
{ |
|
fs.stat(info.fname, function (err) |
|
{ |
|
expect(err.code).to.equal('ENOENT'); |
|
done(); |
|
}); |
|
}); |
|
}); |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
fsq.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
} |
|
|
|
it('should guard against calling subscribe callback twice', function (done) |
|
{ |
|
fsq.on('warning', function (err) |
|
{ |
|
if (err && (err.code !== 'EBUSY')) |
|
{ |
|
throw new Error('should not be called'); |
|
} |
|
}); |
|
|
|
/*jslint unparam: true */ |
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
expect(info.single).to.equal(single_supported); |
|
|
|
cb(null, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
setTimeout(function () |
|
{ |
|
cb(null, done); |
|
}, 2000); |
|
}); |
|
}, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
fsq.publish('foo', 'bar', { single: single_supported }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
/*jslint unparam: false */ |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
it('should only give work to one worker', function (done) |
|
{ |
|
this.timeout(30000); |
|
|
|
var fsq2 = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }), |
|
called = false; |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
function handler (data, info, cb) |
|
{ |
|
expect(called).to.equal(false); |
|
called = true; |
|
|
|
expect(info.topic).to.equal('foo'); |
|
expect(info.single).to.equal(true); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
|
|
setTimeout(function () |
|
{ |
|
cb(null, function (err) |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
done(err); |
|
}); |
|
}); |
|
}, 2000); |
|
} |
|
|
|
fsq.subscribe('foo', function () { handler.apply(this, arguments); }); |
|
fsq.subscribe('foo', function () { handler.apply(this, arguments); }); |
|
|
|
fsq2.subscribe('foo', function () { handler.apply(this, arguments); }); |
|
fsq2.subscribe('foo', function () { handler.apply(this, arguments); }); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
|
|
it('should put work back on the queue', function (done) |
|
{ |
|
var count = 0; |
|
|
|
/*jslint unparam: true */ |
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
count += 1; |
|
|
|
if (count === 1) |
|
{ |
|
cb('dummy failure'); |
|
} |
|
else |
|
{ |
|
cb(null, done); |
|
} |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
} |
|
|
|
it('should allow handlers to refuse work', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
function handler1() |
|
{ |
|
throw new Error('should not be called'); |
|
} |
|
|
|
var fsq2; |
|
|
|
/*jslint unparam: true */ |
|
function handler2(data, info, cb) |
|
{ |
|
cb(null, function (err) |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
done(err); |
|
}); |
|
}); |
|
} |
|
/*jslint unparam: false */ |
|
|
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
filter: function (info, handlers, cb) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
handlers.delete(handler1); |
|
cb(null, true, handlers); |
|
} |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', handler1); |
|
fsq2.subscribe('foo', handler2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
it('should put work back on queue for another handler', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
/*jslint unparam: true */ |
|
function handler(data, info, cb) |
|
{ |
|
cb('dummy failure'); |
|
} |
|
/*jslint unparam: false */ |
|
|
|
var filter_called = false, |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
filter: function (info, handlers, cb) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(info.single).to.equal(true); |
|
|
|
if (filter_called) |
|
{ |
|
handlers.delete(handler); |
|
return cb(null, true, handlers); |
|
} |
|
|
|
filter_called = true; |
|
cb(null, true, handlers); |
|
} |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', handler); |
|
|
|
/*jslint unparam: true */ |
|
fsq2.subscribe('foo', function (data, info, cb) |
|
{ |
|
if (filter_called) |
|
{ |
|
cb(null, function (err) |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
done(err); |
|
}); |
|
}); |
|
} |
|
else |
|
{ |
|
cb('dummy failure2'); |
|
} |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should put work back on queue for a handler on another queue', function (done) |
|
{ |
|
this.timeout(30000); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
/*jslint unparam: true */ |
|
function handler(data, info, cb) |
|
{ |
|
cb('dummy failure'); |
|
} |
|
/*jslint unparam: false */ |
|
|
|
var filter_called = false, |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
filter: function (info, handlers, cb) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(info.single).to.equal(true); |
|
|
|
if (filter_called) |
|
{ |
|
handlers.delete(handler); |
|
} |
|
|
|
filter_called = true; |
|
cb(null, true, handlers); |
|
} |
|
}), |
|
fsq3 = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }), |
|
started2 = false, |
|
started3 = false; |
|
|
|
ignore_ebusy(fsq2); |
|
ignore_ebusy(fsq3); |
|
|
|
fsq2.subscribe('foo', handler); |
|
|
|
/*jslint unparam: true */ |
|
fsq3.subscribe('foo', function (data, info, cb) |
|
{ |
|
if (filter_called) |
|
{ |
|
cb(null, function (err) |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
fsq3.stop_watching(function () |
|
{ |
|
done(err); |
|
}); |
|
}); |
|
}); |
|
} |
|
else |
|
{ |
|
cb('dummy failure2'); |
|
} |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
function start() |
|
{ |
|
if (!(started2 && started3)) { return; } |
|
|
|
fsq2.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
} |
|
|
|
fsq2.on('start', function () |
|
{ |
|
started2 = true; |
|
start(); |
|
}); |
|
|
|
fsq3.on('start', function () |
|
{ |
|
started3 = true; |
|
start(); |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
it('should allow handlers to delay a message', function (done) |
|
{ |
|
restore(); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
var ready_multi = false, |
|
ready_single = !single_supported, |
|
got_multi = false, |
|
got_single = !single_supported, |
|
count = 0, |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
filter: function (info, handlers, cb) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
|
|
if (info.single) |
|
{ |
|
ready_single = true; |
|
} |
|
else |
|
{ |
|
ready_multi = true; |
|
} |
|
|
|
if (!single_supported) |
|
{ |
|
expect(info.single).to.equal(false); |
|
} |
|
|
|
count += 1; |
|
cb(null, (count % 5) === 0, handlers); |
|
} |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
function handler(data, info, cb) |
|
{ |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
|
|
if (info.single) |
|
{ |
|
expect(got_single).to.equal(false); |
|
got_single = true; |
|
} |
|
else |
|
{ |
|
expect(got_multi).to.equal(false); |
|
got_multi = true; |
|
} |
|
|
|
if (!single_supported) |
|
{ |
|
expect(info.single).to.equal(false); |
|
} |
|
|
|
if (got_single && got_multi && ready_single && ready_multi) |
|
{ |
|
expect(count).to.equal(single_supported ? 10 : 5); |
|
|
|
cb(null, function (err) |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
done(err); |
|
}); |
|
}); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
} |
|
|
|
fsq2.subscribe('foo', handler); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
fsq2.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should emit start and stop events', function (done) |
|
{ |
|
this.timeout(30000); |
|
|
|
var fsq2 = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }); |
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.stop_watching(); |
|
fsq2.on('stop', done); |
|
}); |
|
}); |
|
|
|
it('should support per-message time-to-live', function (done) |
|
{ |
|
this.timeout(10000); |
|
|
|
restore(); |
|
|
|
fsq.subscribe('foo', function () |
|
{ |
|
setTimeout(function () |
|
{ |
|
fsq.force_refresh(); |
|
setTimeout(function () |
|
{ |
|
check_empty(msg_dir, done, done); |
|
}, 500); |
|
}, 500); |
|
}); |
|
|
|
fsq.publish('foo', 'bar', { ttl: 500 }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should call error function', function (done) |
|
{ |
|
restore(); |
|
|
|
fsq.on('warning', function (err) |
|
{ |
|
expect(err).to.equal('dummy failure'); |
|
done(); |
|
}); |
|
|
|
/*jslint unparam: true */ |
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
cb('dummy failure'); |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq.publish('foo', 'bar', { single : single_supported }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
it('should support custom polling interval', function (done) |
|
{ |
|
this.timeout(30000); |
|
|
|
restore(); |
|
|
|
var time, count = 0, fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
poll_interval: 50 |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
/*jslint unparam: true */ |
|
fsq2.subscribe('foo', function (data, info, cb) |
|
{ |
|
count += 1; |
|
|
|
var time2 = new Date().getTime(); |
|
expect(time2 - time).to.be.below(900); |
|
time = time2; |
|
|
|
if (count === 10) |
|
{ |
|
cb(null, function () |
|
{ |
|
fsq2.stop_watching(done); |
|
}); |
|
} |
|
else |
|
{ |
|
cb('dummy failure'); |
|
} |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq2.on('start', function () |
|
{ |
|
time = new Date().getTime(); |
|
|
|
fsq.publish('foo', 'bar', {single : true}, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
it('should support unsubscribing', function (done) |
|
{ |
|
this.timeout(5000); |
|
|
|
var count = 0; |
|
|
|
/*jslint unparam: true */ |
|
function handler(data, info, cb) |
|
{ |
|
count += 1; |
|
|
|
if (count > 1) |
|
{ |
|
throw new Error('should not be called'); |
|
} |
|
|
|
fsq.unsubscribe('foo', handler, function () |
|
{ |
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
setTimeout(function () |
|
{ |
|
cb(null, done); |
|
}, 2000); |
|
}); |
|
} |
|
/*jslint unparam: false */ |
|
|
|
fsq.subscribe('foo', handler); |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should support unsubscribing to all handlers for a topic', function (done) |
|
{ |
|
this.timeout(5000); |
|
|
|
var count = 0; |
|
|
|
/*jslint unparam: true */ |
|
function handler(data, info, cb) |
|
{ |
|
count += 1; |
|
|
|
if (count > 2) |
|
{ |
|
throw new Error('should not be called'); |
|
} |
|
|
|
if (count === 2) |
|
{ |
|
fsq.unsubscribe('foo', undefined, function () |
|
{ |
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
setTimeout(function () |
|
{ |
|
cb(null, done); |
|
}, 2000); |
|
}); |
|
} |
|
} |
|
/*jslint unparam: false */ |
|
|
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
handler(data, info, cb); |
|
}); |
|
|
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
handler(data, info, cb); |
|
}); |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should support unsubscribing to all handlers', function (done) |
|
{ |
|
this.timeout(5000); |
|
|
|
var count = 0; |
|
|
|
/*jslint unparam: true */ |
|
function handler(data, info, cb) |
|
{ |
|
count += 1; |
|
|
|
if (count > 2) |
|
{ |
|
throw new Error('should not be called'); |
|
} |
|
|
|
if (count === 2) |
|
{ |
|
fsq.subscribe('foo2', function () |
|
{ |
|
throw new Error('should not be called'); |
|
}); |
|
|
|
fsq.unsubscribe(function () |
|
{ |
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
fsq.publish('foo2', 'bar2', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
setTimeout(function () |
|
{ |
|
cb(null, done); |
|
}, 2000); |
|
}); |
|
} |
|
} |
|
/*jslint unparam: false */ |
|
|
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
handler(data, info, cb); |
|
}); |
|
|
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
handler(data, info, cb); |
|
}); |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should support changing the default time-to-live', function (done) |
|
{ |
|
this.timeout(30000); |
|
|
|
restore(); |
|
|
|
fsq.stop_watching(function () // stop fsq dequeuing |
|
{ |
|
var got_single = !single_supported, |
|
got_multi = false, |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
multi_ttl: 1000, |
|
single_ttl: 1000 |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
/*jslint unparam: true */ |
|
fsq2.subscribe('foo', function (data, info, cb) |
|
{ |
|
if (info.single) |
|
{ |
|
got_single = true; |
|
} |
|
else |
|
{ |
|
got_multi = true; |
|
} |
|
|
|
if (got_single && got_multi) |
|
{ |
|
setTimeout(function () |
|
{ |
|
fsq2.force_refresh(); |
|
setTimeout(function () |
|
{ |
|
check_empty(msg_dir, done, function () |
|
{ |
|
fsq2.stop_watching(done); |
|
}); |
|
}, 1000); |
|
}, 1000); |
|
} |
|
|
|
cb(); |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
fsq.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
} |
|
}); |
|
}); |
|
}); |
|
|
|
it('should publish and receive twice', function (done) |
|
{ |
|
var count_multi = 0, |
|
count_single = single_supported ? 0 : 2; |
|
|
|
/*jslint unparam: true */ |
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
if (info.single) |
|
{ |
|
count_single += 1; |
|
} |
|
else |
|
{ |
|
count_multi += 1; |
|
} |
|
|
|
if ((count_single === 2) && (count_multi === 2)) |
|
{ |
|
cb(null, done); |
|
} |
|
else |
|
{ |
|
if ((count_single > 2) || (count_multi > 2)) |
|
{ |
|
throw new Error('called too many times'); |
|
} |
|
|
|
cb(); |
|
} |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
/*jslint unparam: true */ |
|
async.timesSeries(2, function (n, cb) |
|
{ |
|
async.eachSeries([true, false], function (single, cb) |
|
{ |
|
fsq.publish('foo', 'bar', { single: single }, function (err) |
|
{ |
|
cb(err); |
|
}); |
|
}, cb); |
|
}, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
/*jslint unparam: false */ |
|
}); |
|
|
|
it('should default to putting messages in module directory', function (done) |
|
{ |
|
var fsq2 = new QlobberFSQ({ flags: flags }); |
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', function () |
|
{ |
|
throw new Error('should not be called'); |
|
}); |
|
|
|
fsq2.subscribe('foo2', function (data, info, cb) |
|
{ |
|
expect(data.toString('utf8')).to.equal('bar2'); |
|
expect(info.path.lastIndexOf(path.join(__dirname, '..', 'fsq', 'messages'), 0)).to.equal(0); |
|
cb(null, function () |
|
{ |
|
fsq2.stop_watching(done); |
|
}); |
|
}); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
|
|
// wait for publish so EBUSY isn't retrying while fsq is being cleaned up |
|
|
|
fsq2.publish('foo2', 'bar2', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should publish and subscribe to messages with long topics (multi)', function (done) |
|
{ |
|
var arr = [], topic; |
|
arr.length = 64 * 1024 + 1; |
|
topic = arr.join('a'); |
|
|
|
fsq.subscribe(topic, function (data, info) |
|
{ |
|
expect(info.topic).to.equal(topic); |
|
expect(info.single).to.equal(false); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(new Buffer(topic).toString('hex').substr(0, fsq._split_topic_at) + '@', 0)).to.equal(0); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
|
|
var topic_dir = path.dirname(path.dirname(info.topic_path)); |
|
|
|
expect(topic_dir).to.equal(path.join(msg_dir, '..', 'topics')); |
|
|
|
fs.readFile(info.topic_path, function (err, split) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
expect(split.toString('utf8')).to.equal(new Buffer(topic).toString('hex').substr(fsq._split_topic_at)); |
|
|
|
setTimeout(function () |
|
{ |
|
fsq.force_refresh(); |
|
|
|
setTimeout(function () |
|
{ |
|
check_empty(msg_dir, done, function () |
|
{ |
|
check_empty(topic_dir, done, done); |
|
}); |
|
}, 500); |
|
}, 1000); |
|
}); |
|
}); |
|
|
|
fsq.publish(topic, 'bar', { ttl: 1000 }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
it('should publish and subscribe to messages with long topics (single)', function (done) |
|
{ |
|
var arr = [], topic; |
|
arr.length = 64 * 1024 + 1; |
|
topic = arr.join('a'); |
|
|
|
fsq.subscribe(topic, function (data, info, cb) |
|
{ |
|
expect(info.topic).to.equal(topic); |
|
expect(info.single).to.equal(true); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(new Buffer(topic).toString('hex').substr(0, fsq._split_topic_at) + '@', 0)).to.equal(0); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
|
|
var topic_dir = path.dirname(path.dirname(info.topic_path)); |
|
|
|
expect(topic_dir).to.equal(path.join(msg_dir, '..', 'topics')); |
|
|
|
fs.readFile(info.topic_path, function (err, split) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
expect(split.toString('utf8')).to.equal(new Buffer(topic).toString('hex').substr(fsq._split_topic_at)); |
|
|
|
setTimeout(function () |
|
{ |
|
fsq.force_refresh(); |
|
|
|
setTimeout(function () |
|
{ |
|
check_empty(msg_dir, done, function () |
|
{ |
|
check_empty(topic_dir, done, done); |
|
}); |
|
}, 500); |
|
}, 1000); |
|
|
|
cb(); |
|
}); |
|
}); |
|
|
|
fsq.publish(topic, 'bar', { ttl: 1000, single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
} |
|
|
|
it('should be able to change when a topic file is created', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var topic = 'hellofromfsq', |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
split_topic_at: 5, |
|
retry_interval: retry_interval |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.subscribe(topic, function (data, info) |
|
{ |
|
expect(info.topic).to.equal(topic); |
|
expect(info.single).to.equal(false); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(new Buffer(topic).toString('hex').substr(0, 5) + '@', 0)).to.equal(0); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
|
|
var topic_dir = path.dirname(path.dirname(info.topic_path)); |
|
|
|
expect(topic_dir).to.equal(path.join(msg_dir, '..', 'topics')); |
|
|
|
fs.readFile(info.topic_path, function (err, split) |
|
{ |
|
if (err) { return done(err); } |
|
expect(split.toString('utf8')).to.equal(new Buffer(topic).toString('hex').substr(5)); |
|
|
|
setTimeout(function () |
|
{ |
|
fsq2.force_refresh(); |
|
|
|
setTimeout(function () |
|
{ |
|
fsq2.stop_watching(function () |
|
{ |
|
check_empty(msg_dir, done, function () |
|
{ |
|
check_empty(topic_dir, done, done); |
|
}); |
|
}); |
|
}, 5 * 1000); |
|
}, 1000); |
|
}); |
|
}); |
|
|
|
fsq2.publish(topic, 'bar', { ttl: 1000 }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should not read multi-worker messages which already exist', function (done) |
|
{ |
|
this.timeout (10 * 1000); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
var fsq2 = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }); |
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', function () |
|
{ |
|
done('should not be called'); |
|
}); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
setTimeout(function () |
|
{ |
|
fsq2.stop_watching(done); |
|
}, 5 * 1000); |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
it('should read single worker messages which already exist', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
fsq.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
var fsq2 = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }); |
|
ignore_ebusy(fsq2); |
|
|
|
/*jslint unparam: true */ |
|
fsq2.subscribe('foo', function (data, info, cb) |
|
{ |
|
cb(null, function () |
|
{ |
|
fsq2.stop_watching(done); |
|
}); |
|
}); |
|
/*jslint unparam: false */ |
|
}); |
|
}); |
|
}); |
|
|
|
it('should read single worker messages which already exist after subscribing', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
fsq.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
var fsq2 = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }); |
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
/*jslint unparam: true */ |
|
fsq2.subscribe('foo', function (data, info, cb) |
|
{ |
|
cb(null, function () |
|
{ |
|
fsq2.stop_watching(done); |
|
}); |
|
}); |
|
/*jslint unparam: false */ |
|
}); |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
it('should support streaming interfaces', function (done) |
|
{ |
|
var stream_multi, |
|
stream_single, |
|
stream_file, |
|
sub_multi_called = false, |
|
sub_single_called = !single_supported, |
|
pub_multi_called = false, |
|
pub_single_called = false; |
|
|
|
function handler(stream, info, cb) |
|
{ |
|
if (info.single) |
|
{ |
|
expect(sub_single_called).to.equal(false); |
|
sub_single_called = true; |
|
} |
|
else |
|
{ |
|
expect(sub_multi_called).to.equal(false); |
|
sub_multi_called = true; |
|
} |
|
|
|
var hash = crypto.createHash('sha256'), |
|
len = 0; |
|
|
|
stream.on('readable', function () |
|
{ |
|
var chunk = stream.read(); |
|
|
|
if (chunk) |
|
{ |
|
len += chunk.length; |
|
hash.update(chunk); |
|
} |
|
}); |
|
|
|
stream.on('end', function () |
|
{ |
|
expect(len).to.equal(1024 * 1024); |
|
expect(hash.digest('hex')).to.equal('268e1a23a9da868b62b12e020061c98449568c4af9cf9070c8738fe1b457ed9c'); |
|
|
|
if (pub_multi_called && pub_single_called && |
|
sub_multi_called && sub_single_called) |
|
{ |
|
cb(null, done); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
}); |
|
} |
|
|
|
handler.accept_stream = true; |
|
|
|
fsq.subscribe('foo', handler); |
|
|
|
function published(err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
if (pub_multi_called && pub_single_called && |
|
sub_multi_called && sub_single_called) |
|
{ |
|
done(); |
|
} |
|
} |
|
|
|
stream_multi = fsq.publish('foo', function (err) |
|
{ |
|
expect(pub_multi_called).to.equal(false); |
|
pub_multi_called = true; |
|
published(err); |
|
}); |
|
|
|
stream_single = fsq.publish('foo', { single: true }, function (err) |
|
{ |
|
expect(pub_single_called).to.equal(false); |
|
pub_single_called = true; |
|
published(err); |
|
}); |
|
|
|
stream_file = fs.createReadStream(path.join(__dirname, 'fixtures', 'random')); |
|
stream_file.pipe(stream_multi); |
|
stream_file.pipe(stream_single); |
|
}); |
|
|
|
it('should pipe to more than one stream', function (done) |
|
{ |
|
var stream_mod = require('stream'), |
|
done1 = false, |
|
done2 = false; |
|
|
|
function CheckStream() |
|
{ |
|
stream_mod.Writable.call(this); |
|
|
|
this._hash = crypto.createHash('sha256'); |
|
this._len = 0; |
|
|
|
var ths = this; |
|
|
|
this.on('finish', function () |
|
{ |
|
ths.emit('done', |
|
{ |
|
digest: ths._hash.digest('hex'), |
|
len: ths._len |
|
}); |
|
}); |
|
} |
|
|
|
util.inherits(CheckStream, stream_mod.Writable); |
|
|
|
/*jslint unparam: true */ |
|
CheckStream.prototype._write = function (chunk, encoding, callback) |
|
{ |
|
this._len += chunk.length; |
|
this._hash.update(chunk); |
|
callback(); |
|
}; |
|
/*jslint unparam: false */ |
|
|
|
function check(obj, cb) |
|
{ |
|
expect(obj.len).to.equal(1024 * 1024); |
|
expect(obj.digest).to.equal('268e1a23a9da868b62b12e020061c98449568c4af9cf9070c8738fe1b457ed9c'); |
|
|
|
if (done1 && done2) |
|
{ |
|
cb(null, done); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
} |
|
|
|
/*jslint unparam: true */ |
|
function handler1(stream, info, cb) |
|
{ |
|
var cs = new CheckStream(); |
|
|
|
cs.on('done', function (obj) |
|
{ |
|
done1 = true; |
|
check(obj, cb); |
|
}); |
|
|
|
stream.pipe(cs); |
|
} |
|
|
|
function handler2(stream, info, cb) |
|
{ |
|
var cs = new CheckStream(); |
|
|
|
cs.on('done', function (obj) |
|
{ |
|
done2 = true; |
|
check(obj, cb); |
|
}); |
|
|
|
stream.pipe(cs); |
|
} |
|
/*jslint unparam: false */ |
|
|
|
handler1.accept_stream = true; |
|
fsq.subscribe('foo', handler1); |
|
|
|
handler2.accept_stream = true; |
|
fsq.subscribe('foo', handler2); |
|
|
|
fs.createReadStream(path.join(__dirname, 'fixtures', 'random')).pipe( |
|
fsq.publish('foo', function (err) |
|
{ |
|
if (err) { return done(err); } |
|
})); |
|
}); |
|
|
|
it('should not call the same handler with stream and data', function (done) |
|
{ |
|
/*jslint unparam: true */ |
|
function handler(stream, info, cb) |
|
{ |
|
expect(Buffer.isBuffer(stream)).to.equal(false); |
|
|
|
var hash = crypto.createHash('sha256'), |
|
len = 0; |
|
|
|
stream.on('readable', function () |
|
{ |
|
var chunk = stream.read(); |
|
|
|
if (chunk) |
|
{ |
|
len += chunk.length; |
|
hash.update(chunk); |
|
} |
|
}); |
|
|
|
stream.on('end', function () |
|
{ |
|
expect(len).to.equal(1024 * 1024); |
|
expect(hash.digest('hex')).to.equal('268e1a23a9da868b62b12e020061c98449568c4af9cf9070c8738fe1b457ed9c'); |
|
cb(null, done); |
|
}); |
|
} |
|
/*jslint unparam: false */ |
|
|
|
handler.accept_stream = true; |
|
|
|
fsq.subscribe('foo', handler); |
|
|
|
fs.createReadStream(path.join(__dirname, 'fixtures', 'random')).pipe( |
|
fsq.publish('foo', function (err) |
|
{ |
|
if (err) { return done(err); } |
|
})); |
|
}); |
|
|
|
it('should use inotify to process messages straight away', function (done) |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
poll_interval: 10 * 1000 |
|
}), time; |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', function () |
|
{ |
|
expect(new Date().getTime() - time).to.be.below(fsq2._poll_interval); |
|
fsq2.stop_watching(done); |
|
}); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
time = new Date().getTime(); |
|
fsq2.publish('foo', 'bar'); |
|
}); |
|
}); |
|
|
|
it('should be able to disable inotify', function (done) |
|
{ |
|
restore(); |
|
|
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
poll_interval: 10 * 1000, |
|
notify: false |
|
}), time; |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', function () |
|
{ |
|
expect(new Date().getTime() - time).to.be.at.least(fsq2._poll_interval); |
|
fsq2.stop_watching(done); |
|
}); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
time = new Date().getTime(); |
|
fsq2.publish('foo', 'bar', { ttl: 30 * 1000 }); |
|
}); |
|
}); |
|
|
|
it('should be able to change the size of update stamps', function (done) |
|
{ |
|
fs.stat(path.join(msg_dir, '..', 'update', 'UPDATE'), function (err, stats) |
|
{ |
|
if (err) { return done(err); } |
|
expect(stats.size).to.equal(Math.pow(16, 2) * 32); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
bucket_stamp_size: 64 |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', function (data) |
|
{ |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
fsq2.stop_watching(done); |
|
}); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fs.stat(path.join(msg_dir, '..', 'update', 'UPDATE'), function (err, stats) |
|
{ |
|
if (err) { return done(err); } |
|
expect(stats.size).to.equal(Math.pow(16, 2) * 64); |
|
fsq2.publish('foo', 'bar'); |
|
}); |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should be able to change the number of random bytes at the end of filenames', function (done) |
|
{ |
|
/*jslint unparam: true */ |
|
fsq.subscribe('foo', function (data, info) |
|
{ |
|
var split = info.fname.split('+'), fsq2; |
|
expect(split[split.length - 1].length).to.equal(32); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
unique_bytes: 8 |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', function (data, info) |
|
{ |
|
var split2 = info.fname.split('+'); |
|
expect(split2[split2.length - 1].length).to.equal(16); |
|
fsq2.stop_watching(done); |
|
}); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.publish('foo', 'bar'); |
|
}); |
|
}); |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq.publish('foo', 'bar'); |
|
}); |
|
|
|
it('should read one message at a time by default', function (done) |
|
{ |
|
this.timeout(5 * 60 * 1000); |
|
|
|
restore(); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
var in_call = false, |
|
count = 0, |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
poll_interval: 10 * 1000, |
|
notify: false |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
/*jslint unparam: true */ |
|
function handler (stream, info, cb) |
|
{ |
|
expect(in_call).to.equal(false); |
|
|
|
in_call = true; |
|
count += 1; |
|
|
|
stream.on('end', function () |
|
{ |
|
in_call = false; |
|
cb(null, count === 5 ? function () |
|
{ |
|
fsq2.stop_watching(done); |
|
} : null); |
|
}); |
|
|
|
if (count === 5) |
|
{ |
|
stream.on('data', function () { return undefined; }); |
|
} |
|
else |
|
{ |
|
// give time for other reads to start |
|
setTimeout(function () |
|
{ |
|
stream.on('data', function () { return undefined; }); |
|
}, 5 * 1000); |
|
} |
|
} |
|
/*jslint unparam: false */ |
|
|
|
handler.accept_stream = true; |
|
|
|
fsq2.subscribe('foo', handler); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
var i; |
|
|
|
function cb(err) |
|
{ |
|
if (err) { done(err); } |
|
} |
|
|
|
for (i = 0; i < 5; i += 1) |
|
{ |
|
fsq2.publish('foo', 'bar', { ttl: 2 * 60 * 1000 }, cb); |
|
} |
|
}); |
|
}); |
|
}); |
|
|
|
it('should be able to read more than one message at a time', function (done) |
|
{ |
|
this.timeout(5 * 60 * 1000); |
|
|
|
restore(); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
var in_call = 0, |
|
count = 0, |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
poll_interval: 10 * 1000, |
|
notify: false, |
|
bucket_base: 10, |
|
bucket_num_chars: 2, |
|
bucket_concurrency: 5, |
|
message_concurrency: 2 |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
/*jslint unparam: true */ |
|
function handler(stream, info, cb) |
|
{ |
|
expect(in_call).to.be.at.most(9); |
|
|
|
in_call += 1; |
|
count += 1; |
|
|
|
stream.on('end', function () |
|
{ |
|
in_call -= 1; |
|
cb(null, (count === 25) && (in_call === 0) ? function () |
|
{ |
|
fsq2.stop_watching(done); |
|
} : null); |
|
}); |
|
|
|
if (count === 25) |
|
{ |
|
stream.on('data', function () { return undefined; }); |
|
} |
|
else |
|
{ |
|
// give time for other reads to start |
|
setTimeout(function () |
|
{ |
|
stream.on('data', function () { return undefined; }); |
|
}, 5 * 1000); |
|
} |
|
} |
|
/*jslint unparam: false */ |
|
|
|
handler.accept_stream = true; |
|
|
|
fsq2.subscribe('foo', handler); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
var i; |
|
|
|
function cb(err) |
|
{ |
|
if (err) { done(err); } |
|
} |
|
|
|
for (i = 0; i < 25; i += 1) |
|
{ |
|
fsq2.publish('foo', 'bar', { ttl: 2 * 60 * 1000 }, cb); |
|
} |
|
}); |
|
}); |
|
}); |
|
|
|
it('should clear up expired messages', function (done) |
|
{ |
|
var num_queues = 100, //6000 |
|
num_messages = 500; |
|
|
|
restore(); |
|
|
|
this.timeout(10 * 60 * 1000); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
lsof.counters(function (open_before) |
|
{ |
|
/*jslint unparam: true */ |
|
async.times(num_queues, function (n, cb) |
|
{ |
|
var fsq = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }); |
|
|
|
ignore_ebusy(fsq, os.platform() === 'win32' ? 'EPERM' : null); |
|
|
|
fsq.on('start', function () |
|
{ |
|
cb(null, fsq); |
|
}); |
|
}, function (err, fsqs) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
expect(fsqs.length).to.equal(num_queues); |
|
|
|
async.timesSeries(num_messages, function (n, cb) |
|
{ |
|
fsq.publish('foo', 'bar', { ttl: 2 * 1000 }, cb); |
|
}, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
|
|
setTimeout(function () |
|
{ |
|
async.each(fsqs, function (fsq, next) |
|
{ |
|
fsq.subscribe('foo', function () |
|
{ |
|
throw new Error('should not be called'); |
|
}); |
|
fsq.force_refresh(); |
|
next(); |
|
}, function () |
|
{ |
|
setTimeout(function () |
|
{ |
|
async.each(fsqs, function (fsq, cb) |
|
{ |
|
fsq.stop_watching(cb); |
|
}, function () |
|
{ |
|
check_empty(msg_dir, done, function () |
|
{ |
|
lsof.counters(function (open_after) |
|
{ |
|
expect(open_after.open).to.equal(open_before.open); |
|
done(); |
|
}); |
|
}); |
|
}); |
|
}, 60 * 1000); |
|
}); |
|
}, 2 * 1000); |
|
}); |
|
}); |
|
/*jslint unparam: false */ |
|
}); |
|
}); |
|
}); |
|
|
|
if (single_supported) |
|
{ |
|
it('should clear up expired message while worker has it locked', function (done) |
|
{ |
|
this.timeout(60 * 1000); |
|
|
|
restore(); |
|
|
|
/*jslint unparam: true */ |
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
setTimeout(function () |
|
{ |
|
check_empty(msg_dir, done, function () |
|
{ |
|
cb(null, done); |
|
}); |
|
}, 30 * 1000); |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq.publish('foo', 'bar', { single: true, ttl: 500 }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
} |
|
|
|
function bucket_names(base, chars) |
|
{ |
|
var n = Math.pow(base, chars), i, s, r = [], arr = []; |
|
|
|
for (i = 0; i < n; i += 1) |
|
{ |
|
s = base > 1 ? i.toString(base) : '0'; |
|
arr.length = chars + 1; |
|
r.push((arr.join('0') + s).slice(-chars)); |
|
} |
|
|
|
return r; |
|
} |
|
|
|
function test_buckets(base, chars) |
|
{ |
|
it('should distribute messages between bucket directories (base=' + base + ', chars=' + chars + ')', function (done) |
|
{ |
|
// This _could_ fail if the hash function happens not to distribute |
|
// at least one message into each bucket. |
|
|
|
var timeout = 10 * 60 * 1000, |
|
buckets = {}, |
|
count = 0, |
|
num, |
|
fsq2; |
|
|
|
this.timeout(timeout); |
|
|
|
function go() |
|
{ |
|
num = Math.pow(base, chars) * 15; |
|
|
|
/*jslint unparam: true */ |
|
fsq2.subscribe('foo', function (data, info) |
|
{ |
|
var mdir = path.join(path.dirname(info.path), '..'); |
|
expect(mdir).to.equal(msg_dir); |
|
|
|
buckets[path.basename(path.dirname(info.path))] = true; |
|
|
|
count += 1; |
|
|
|
if (count === num) |
|
{ |
|
fs.readdir(mdir, function (err, files) |
|
{ |
|
var names = bucket_names(base, chars); |
|
if (err) { return done(err); } |
|
expect(files.sort()).to.eql(names); |
|
expect(Object.keys(buckets).sort()).to.eql(names); |
|
fsq2.stop_watching(done); |
|
}); |
|
} |
|
else if (count > num) |
|
{ |
|
throw "called too many times"; |
|
} |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
/*jslint unparam: true */ |
|
var q = async.queue(function (task, cb) |
|
{ |
|
fsq2.publish('foo', 'bar', { ttl: timeout }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
cb(); |
|
}); |
|
}, 5), i; |
|
/*jslint unparam: false */ |
|
|
|
for (i = 0; i < num; i += 1) |
|
{ |
|
q.push(i); |
|
} |
|
} |
|
|
|
if (base === undefined) |
|
{ |
|
base = 16; |
|
chars = 2; |
|
fsq2 = fsq; |
|
go(); |
|
} |
|
else |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
rimraf(fsq_dir, function (err) |
|
{ |
|
if (err) { return done(err); } |
|
fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
bucket_base: base, |
|
bucket_num_chars: chars, |
|
retry_interval: retry_interval |
|
}); |
|
ignore_ebusy(fsq2); |
|
fsq2.on('start', go); |
|
}); |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
test_buckets(); |
|
test_buckets(1, 1); |
|
test_buckets(10, 2); |
|
test_buckets(26, 1); |
|
test_buckets(26, 2); |
|
test_buckets(8, 3); |
|
|
|
it('should emit an error event if an error occurs before a start event', function (done) |
|
{ |
|
var orig_readdir = fs.readdir, fsq2; |
|
|
|
/*jslint unparam: true */ |
|
fs.readdir = function (dir, cb) |
|
{ |
|
cb('dummy error'); |
|
}; |
|
/*jslint unparam: false */ |
|
|
|
fsq2 = new QlobberFSQ({ fsq_dir: fsq_dir, flags: flags }); |
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('error', function (err) |
|
{ |
|
expect(err).to.equal('dummy error'); |
|
fs.readdir = orig_readdir; |
|
done(); |
|
}); |
|
}); |
|
|
|
it('should handle read errors', function (done) |
|
{ |
|
var count = 0, |
|
orig_createReadStream = fs.createReadStream; |
|
|
|
fs.createReadStream = function () |
|
{ |
|
return orig_createReadStream.call(this, ''); |
|
}; |
|
|
|
fsq.on('warning', function (err) |
|
{ |
|
if (err && (err.code === 'ENOENT')) |
|
{ |
|
count += 1; |
|
|
|
if (!single_supported) |
|
{ |
|
fs.createReadStream = orig_createReadStream; |
|
done(); |
|
} |
|
else if (count === 5) // check single repeats |
|
{ |
|
fs.createReadStream = orig_createReadStream; |
|
} |
|
} |
|
}); |
|
|
|
/*jslint unparam: true */ |
|
fsq.subscribe('foo', function (data, info, cb) |
|
{ |
|
expect(info.single).to.equal(true); // we throw multi away on error |
|
cb(null, done); |
|
}); |
|
/*jslint unparam: false */ |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
fsq.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should pass back write errors when publishing', function (done) |
|
{ |
|
var orig_createWriteStream = fs.createWriteStream; |
|
|
|
fs.createWriteStream = function () |
|
{ |
|
return orig_createWriteStream.call(this, ''); |
|
}; |
|
|
|
fsq.publish('foo', 'bar', function (err) |
|
{ |
|
expect(err.code).to.equal('ENOENT'); |
|
fs.createWriteStream = orig_createWriteStream; |
|
done(); |
|
}); |
|
}); |
|
|
|
it('should support disabling work queue (single messages)', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
single: false |
|
}), called = false; |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.subscribe('foo', function (data, info, cb) |
|
{ |
|
expect(info.topic).to.equal('foo'); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
// should never be called with 'single' messages |
|
expect(info.single).to.equal(false); |
|
called = true; |
|
}); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
fsq2.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
fsq2.publish('foo', 'bar', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
setTimeout(function () |
|
{ |
|
expect(called).to.equal(true); |
|
fsq2.stop_watching(done); |
|
}, 10000); |
|
}); |
|
}); |
|
|
|
it('should disable work queue (single messages) if fs-ext is not available', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags |
|
}); |
|
|
|
fsq2._require_fs = function (fs) |
|
{ |
|
if (fs !== 'fs') |
|
{ |
|
throw new Error('dummy'); |
|
} |
|
|
|
return require(fs); |
|
}; |
|
|
|
fsq2.on('start', function () |
|
{ |
|
expect(this._do_single).to.equal(false); |
|
fsq2.stop_watching(done); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should emit event when fs-ext is not available', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags |
|
}); |
|
|
|
fsq2._require_fs = function (fs) |
|
{ |
|
if (fs !== 'fs') |
|
{ |
|
throw 'dummy'; |
|
} |
|
|
|
return require(fs); |
|
}; |
|
|
|
fsq2.on('single_disabled', function (err) |
|
{ |
|
expect(this._do_single).to.equal(false); |
|
expect(err).to.equal('dummy'); |
|
fsq2.stop_watching(done); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should publish to a topic with an invalid file name character', function (done) |
|
{ |
|
var arr = [], ltopic, rsingle = !single_supported, rmulti = false; |
|
arr.length = 64 * 1024 + 1; |
|
ltopic = arr.join('\0'); |
|
|
|
fsq.subscribe('*', function (data, info) |
|
{ |
|
if (info.single) |
|
{ |
|
expect(rsingle).to.equal(false); |
|
rsingle = true; |
|
expect(info.topic).to.equal(ltopic); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(new Buffer(ltopic).toString('hex').substr(0, fsq._split_topic_at) + '@', 0)).to.equal(0); |
|
expect(data.toString('utf8')).to.equal('test'); |
|
|
|
var topic_dir = path.dirname(path.dirname(info.topic_path)); |
|
expect(topic_dir).to.equal(path.join(msg_dir, '..', 'topics')); |
|
expect(fs.readFileSync(info.topic_path).toString('utf8')).to.equal(new Buffer(ltopic).toString('hex').substr(fsq._split_topic_at)); |
|
} |
|
else |
|
{ |
|
expect(rmulti).to.equal(false); |
|
rmulti = true; |
|
expect(info.topic).to.equal('\0foo'); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(new Buffer('\0foo').toString('hex') + '@', 0)).to.equal(0); |
|
expect(info.topic_path).to.equal(undefined); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
} |
|
|
|
if (rsingle && rmulti) |
|
{ |
|
this.stop_watching(done); |
|
} |
|
}); |
|
|
|
fsq.publish('\0foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
fsq.publish(ltopic, 'test', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
|
|
it('should error when publishing to a topic with an invalid file name character and topic encoding is disabled', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
encode_topics: false |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
this.publish('\0foo', 'bar', function (err) |
|
{ |
|
if (!err) { return done(new Error('expected an error')); } |
|
if (err.code) // 0.12 doesn't set code |
|
{ |
|
expect(err.code).to.equal('ENOENT'); |
|
} |
|
this.stop_watching(done); |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should not error when publishing to a topic without an invalid file name character and topic encoding is disabled', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
encode_topics: false |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
var arr = [], ltopic, rsingle = !single_supported, rmulti = false; |
|
arr.length = 64 * 1024 + 1; |
|
ltopic = arr.join('a'); |
|
|
|
this.subscribe('*', function (data, info) |
|
{ |
|
if (info.single) |
|
{ |
|
expect(rsingle).to.equal(false); |
|
rsingle = true; |
|
expect(info.topic).to.equal(ltopic); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf(ltopic.substr(0, fsq._split_topic_at) + '@', 0)).to.equal(0); |
|
expect(data.toString('utf8')).to.equal('test'); |
|
|
|
var topic_dir = path.dirname(path.dirname(info.topic_path)); |
|
expect(topic_dir).to.equal(path.join(msg_dir, '..', 'topics')); |
|
expect(fs.readFileSync(info.topic_path).toString('utf8')).to.equal(ltopic.substr(fsq._split_topic_at)); |
|
} |
|
else |
|
{ |
|
expect(rmulti).to.equal(false); |
|
rmulti = true; |
|
expect(info.topic).to.equal('foo'); |
|
expect(info.path.lastIndexOf(msg_dir, 0)).to.equal(0); |
|
expect(info.fname.lastIndexOf('foo' + '@', 0)).to.equal(0); |
|
expect(info.topic_path).to.equal(undefined); |
|
expect(data.toString('utf8')).to.equal('bar'); |
|
} |
|
|
|
if (rsingle && rmulti) |
|
{ |
|
this.stop_watching(done); |
|
} |
|
}); |
|
|
|
this.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
|
|
this.publish(ltopic, 'test', { single: true }, function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should support handler concurrency', function (done) |
|
{ |
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
handler_concurrency: 2 |
|
}); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
var streams = []; |
|
|
|
function handler(s, info) |
|
{ |
|
streams.push(s); |
|
|
|
if (streams.length === 1) |
|
{ |
|
fsq2.publish('foo', 'bar2', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
} |
|
else if (streams.length === 2) |
|
{ |
|
read_all(streams[0], function (v) |
|
{ |
|
expect(v.toString()).to.equal('bar'); |
|
read_all(streams[1], function (v) |
|
{ |
|
expect(v.toString()).to.equal('bar2'); |
|
fsq2.stop_watching(done); |
|
}); |
|
}); |
|
} |
|
else |
|
{ |
|
done(new Error('called too many times')); |
|
} |
|
} |
|
|
|
handler.accept_stream = true; |
|
|
|
this.subscribe('foo', handler); |
|
|
|
this.publish('foo', 'bar', function (err) |
|
{ |
|
if (err) { done(err); } |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should support delivering messages in expiry order', function (done) |
|
{ |
|
restore(); |
|
|
|
fsq.stop_watching(function () |
|
{ |
|
var fsq2 = new QlobberFSQ( |
|
{ |
|
fsq_dir: fsq_dir, |
|
flags: flags, |
|
poll_interval: 60 * 60 * 1000, |
|
notify: false, |
|
order_by_expiry: true, |
|
multi_ttl: 24 * 60 * 60 * 1000 |
|
}); |
|
|
|
expect(fsq2._bucket_base).to.equal(1); |
|
expect(fsq2._bucket_num_chars).to.equal(1); |
|
|
|
ignore_ebusy(fsq2); |
|
|
|
fsq2.on('start', function () |
|
{ |
|
var n = 1000, ttls_out = [], ttls_in = [], expiries_in = [], i; |
|
|
|
// Need to leave enough time between ttls to account for |
|
// time increasing while publishing |
|
for (i = 0; i < n; i += 1) |
|
{ |
|
/*jshint laxbreak: true */ |
|
ttls_out.push(Math.round( |
|
|
|
Math.random() * 18 * 60 // random mins up to 18 hour period |
|
+ 1 * 60) // plus one hour |
|
|
|
* 60 * 1000); // convert to milliseconds |
|
} |
|
|
|
function num_sort(x, y) |
|
{ |
|
return x - y; |
|
} |
|
|
|
fsq2.subscribe('foo', function (data, info) |
|
{ |
|
ttls_in.push(parseInt(data.toString())); |
|
expiries_in.push(info.expires); |
|
|
|
if (ttls_in.length === n) |
|
{ |
|
// check expiries are actually in ascending order |
|
var sorted_expiries_in = expiries_in.concat(); |
|
sorted_expiries_in.sort(num_sort); |
|
expect(expiries_in).to.eql(sorted_expiries_in); |
|
|
|
// check messages are in expected order |
|
ttls_out.sort(num_sort); |
|
expect(ttls_in).to.eql(ttls_out); |
|
|
|
done(); |
|
} |
|
else if (ttls_in.length > n) |
|
{ |
|
done(new Error('called too many times')); |
|
} |
|
}); |
|
|
|
async.eachSeries(ttls_out, function (ttl, cb) |
|
{ |
|
fsq2.publish('foo', ttl.toString(), { ttl: ttl }, cb); |
|
}, function (err) |
|
{ |
|
expect(err).to.equal(null); |
|
expect(ttls_in.length).to.equal(0); |
|
fsq2.refresh_now(); |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
it('should support error on stream and calling back', function (done) |
|
{ |
|
var msg; |
|
|
|
fsq.on('warning', function (err) |
|
{ |
|
if (err.code !== 'EBUSY') |
|
{ |
|
msg = err.message; |
|
} |
|
}); |
|
|
|
var handler = function (stream, info, cb) |
|
{ |
|
stream.emit('error', new Error('dummy')); |
|
cb(new Error('dummy'), function (err) |
|
{ |
|
expect(err).to.equal(null); |
|
expect(msg).to.equal('dummy'); |
|
done(); |
|
}); |
|
}; |
|
handler.accept_stream = true; |
|
|
|
fsq.subscribe('foo', handler); |
|
fsq.publish('foo', { single: true }).end('bar'); |
|
}); |
|
|
|
it('should support calling back before stream has ended', function (done) |
|
{ |
|
var count = 0; |
|
|
|
function handler(stream, info, cb) |
|
{ |
|
count += 1; |
|
|
|
if (count === 2) |
|
{ |
|
cb(null, done); |
|
} |
|
else |
|
{ |
|
cb(); |
|
} |
|
} |
|
handler.accept_stream = true; |
|
|
|
fsq.subscribe('foo', handler); |
|
fsq.publish('foo').end('bar'); |
|
fsq.publish('foo', { single: true }).end('bar'); |
|
}); |
|
|
|
it('should end/error stream after called back before stream has ended', |
|
function (done) |
|
{ |
|
var count = 0; |
|
|
|
function handler(stream, info, cb) |
|
{ |
|
stream.on('readable', function () |
|
{ |
|
expect(this.read()).to.equal(null); |
|
}); |
|
|
|
count += 1; |
|
|
|
if (count === 2) |
|
{ |
|
stream.on('end', done); |
|
cb(); |
|
} |
|
else |
|
{ |
|
var ended = false, msg; |
|
|
|
stream.on('end', function () |
|
{ |
|
ended = true; |
|
}); |
|
|
|
stream.on('error', function (err) |
|
{ |
|
msg = err.message; |
|
}); |
|
|
|
cb(new Error('dummy'), function (err) |
|
{ |
|
expect(err).to.equal(null); |
|
expect(msg).to.equal('dummy'); |
|
|
|
process.nextTick(function () |
|
{ |
|
expect(ended).to.equal(true); |
|
fsq.publish('foo', { single: true }).end('bar'); |
|
}); |
|
}); |
|
} |
|
} |
|
handler.accept_stream = true; |
|
|
|
fsq.subscribe('foo', handler); |
|
fsq.publish('foo').end('bar'); |
|
}); |
|
});
|
|
|