UNPKG

redis-fast-driver

Version:

Fast truly async driver for redis (based on hiredis async version)

336 lines (279 loc) 9.98 kB
'use strict'; const Redis = require('../'); const Promise = require('bluebird'); const assert = require('power-assert'); describe('redis-fast-driver', function() { let redis; afterEach(() => { if (redis) redis.end(); }); function eventPromise(event) { return new Promise((resolve) => redis.once(event, resolve)); } function rawCall(args) { return Promise.fromCallback((cb) => redis.rawCall(args, cb)); } describe('Connecting', function() { it('Connects', async function() { redis = new Redis(); const readyPromise = eventPromise('ready'); const connectPromise = eventPromise('connect'); await Promise.all([readyPromise, connectPromise]); }); it('Does not connect when autoConnect:false', async function() { redis = new Redis({autoConnect: false}); const readyPromise = eventPromise('ready'); const connectPromise = eventPromise('connect'); let isManuallyConnected = false; const prematurePromise = Promise.all([readyPromise, connectPromise]) .then(() => { if (!isManuallyConnected) throw new Error('Connected too early!'); }); const connPromise = Promise.delay(100) .then(() => { isManuallyConnected = true; redis.connect(); }); await Promise.all([prematurePromise, connPromise]); assert(redis.ready); assert(redis.readyFirstTime); }); it('Can be destroyed and revived', async function() { redis = new Redis({ doNotRunQuitOnEnd: true, doNotSetClientName: true, }); await eventPromise('ready'); redis.end(); await eventPromise('end'); assert(!redis.ready); assert(redis.destroyed); redis.init(); await eventPromise('ready'); assert(redis.ready); assert(redis.readyFirstTime); assert(!redis.destroyed); }); it('Reconnects, and emits "end" at limit', async function() { const maxRetries = 10; redis = new Redis({ maxRetries, reconnectTimeout: 0, doNotSetClientName: true, doNotRunQuitOnEnd: true }); // redis.on('connect', () => console.log('connect')); // redis.on('reconnecting', (num) => console.log('reconn', num)); // redis.on('disconnect', () => console.log('disconnect')); // redis.on('end', () => console.log('end')); redis.on('error', () => {}); const endPromise = eventPromise('end'); // Monkey-patch as not to reset the reconnect timer redis.onConnect = function() { redis.emit('connect'); }; await eventPromise('connect'); // Disconnect limit + 1 times await Promise.mapSeries(Array(maxRetries).fill(0), () => { const events = Promise.all([eventPromise('disconnect'), eventPromise('connect')]); redis.redis.disconnect(); return events; }); assert(redis.reconnects === maxRetries); // Once more to kill it redis.redis.disconnect(); await endPromise; }); it('Disconnects if ended during connect (potential race condition)', async function() { redis = new Redis({autoConnect: false, doNotSetClientName: true, doNotRunQuitOnEnd: true }); redis.redis.connect = function(host, port, onConnect, onDisconnect) { setImmediate(onConnect); }; let disconnected = false; redis.redis.disconnect = function() { disconnected = true; }; redis.connect(); redis.end(); assert(redis.destroyed); assert(disconnected === false); await Promise.delay(0); // onConnect() called, realizes destroyed, calls redis.disconnect assert(disconnected === true); }); it('Disconnects if timeout during connect', async function() { const timeout = 20; redis = new Redis({autoConnect: false, doNotSetClientName: true, doNotRunQuitOnEnd: true, connectTimeout: timeout, reconnectTimeout: 0, }); redis.redis.connect = function(host, port, onConnect, onDisconnect) { // setTimeout(onDisconnect, timeout); }; redis.redis.disconnect = function() { // noop to avoid SIGSEGV because we didn't really connect }; let error; redis.on('error', (e) => { error = e; }); redis.on('connect', () => console.log('connected')); redis.connect(); // Allow the adapter to reconnect a bunch of times to assert the state works as we expect let start = Date.now(); let last = 0; for (let i = 0; i < 10; i++) { await new Promise((resolve) => redis.once('reconnecting', resolve)); assert(error.message === 'Connection Timeout.'); assert(redis.reconnects === i + 1); // Assert this took roughly as long as the timeout to fire const timeElapsed = Date.now() - start; assert(timeElapsed >= timeout && timeElapsed <= timeout * 2); start = Date.now(); } // Now actually reconnect redis.redis.connect = function(host, port, onConnect, onDisconnect) { onConnect(); }; await Promise.delay(timeout * 1.5); assert(redis.reconnects === 0); assert(redis.ready); redis.end(); assert(redis.destroyed); }); }); describe('rawCall', function() { beforeEach(async function() { redis = new Redis(); await eventPromise('connect'); }); it('ping', async function() { const resp = await rawCall(['ping']); assert(resp === 'PONG'); }); it('ping (rawCallAsync)', async function() { const resp = await redis.rawCallAsync(['ping']); assert(resp === 'PONG'); }); it('set/get', async function() { const set = await rawCall(['set', 'number', 123]); assert(set === 'OK'); const get = await rawCall(['get', 'number']); assert(get === '123'); }); it('incr', async function() { const incr = await rawCall(['incr', 'number']); assert(incr === 124); // note number type }); it('zrange', async function() { const key = 'sortedset'+new Date; const zadd = await rawCall(['zadd', key, 1, 'a', 2, 'b', 3, 'c']); assert(zadd === 3); const zrange = await rawCall(['zrange', key, 0, -1]); assert.deepEqual(zrange, ['a', 'b', 'c']); }); it('del hset:1', async function() { await rawCall(['del', 'hset:1']); assert(1 === 1); }); it('hmset', async function() { const hmset = await rawCall(['hmset', 'hset:1', 'a', 1, 'b', 2, 'c', 3]); assert(hmset === 'OK'); }); it('hscan', async function() { const hscan = await rawCall(['hscan', 'hset:1', 0]); assert.deepEqual(hscan, ['0', ['a', '1', 'b', '2', 'c', '3']]); }); it('hgetall', async function() { const hgetall = await rawCall(['hgetall', 'hset:1']); assert.deepEqual(hgetall, ['a', '1', 'b', '2', 'c', '3']); }); it('del zset', async function() { const zadd = await rawCall(['del', 'zset:1']); assert(1 === 1); }); it('zadd', async function() { const zadd = await rawCall(['zadd', 'zset:1', 1, 'a', 2, 'b', 3, 'c', 4, 'd', 5, 'e']); assert(zadd === 5); }); it('zrange', async function() { const zrange = await rawCall(['zrange', 'zset:1', 0, -1]); assert.deepEqual(zrange, ['a', 'b', 'c', 'd', 'e']); }); it('works correctly when send buffer needs to be resized', async function() { // This should be the same as RFD_COMMAND_BUFFER_SIZE defined in // redis-fast-driver.h const commandBufferSize = 4096; let bigBuff; for(let i=1;i<8;i++) { //multiply * i for exceeding the buffer size, so buf is 4096*i '+' bigBuff = Array(commandBufferSize*i+1).join('+'); await rawCall(['SET', 'key', bigBuff]); } const result = await rawCall(['GET', 'key']); assert.ok(bigBuff === result); }); it('works correctly when command buffer needs to be resized', async function() { const cmd = ['HMSET', 'hset:1']; for(let i=0;i<=128;i++) { cmd.push('key'+i, 'val'+i); } await rawCall(cmd); const len = await rawCall(['HLEN', 'hset:1']); assert.ok(len > 128); }); describe('errors', function() { it('incr a string', async function() { await rawCall(['set', 'str', 'hello']); try { await rawCall(['incr', 'str']); assert(false); } catch (e) { assert(/not an integer or out of range/.test(e.message)); } }); it('zrange string', async function() { try { await rawCall(['zrange', 'zset:1', 1, 'a', 2, 'b', 3, 'c', 4, 'd']); assert(false); } catch (e) { assert(/syntax error/.test(e.message)); } }); }); }); describe('queueing', function() { beforeEach(async function() { redis = new Redis({ autoConnect: false, doNotSetClientName: true, doNotRunQuitOnEnd: true }); }); it('Queues up messages before connect', async function() { const cmds = [['ping'], ['set', 'number', '1'], ['get', 'number']]; const promises = cmds.map((cmd) => rawCall(cmd)); assert(redis.queue.length === cmds.length); cmds.forEach((val, idx) => { assert.deepEqual(redis.queue[idx].args, val); }); redis.connect(); await eventPromise('connect'); const results = await Promise.all(promises); assert.deepEqual(results, ['PONG', 'OK', '1']); assert(redis.queue.length === 0); }); it('Releases queue on end', async function() { const cmds = [['ping'], ['set', 'number', '1'], ['get', 'number']]; cmds.map((cmd) => rawCall(cmd)); assert(redis.queue.length === cmds.length); redis.end(); assert.deepEqual(redis.queue, []); }); }); });