This repository has been archived by the owner on Feb 18, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
David Ellis
committed
Mar 22, 2014
1 parent
1a02e1a
commit 635f91b
Showing
4 changed files
with
339 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,5 @@ notifications: | |
- d.f.ellis@ieee.org | ||
on_success: change | ||
on_failure: change | ||
|
||
services: redis |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,81 @@ | ||
function cacheRedis() { | ||
|
||
var redisManager = require('redis-manager'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var util = require('util'); | ||
|
||
function CacheRedis(config) { | ||
EventEmitter.call(this); | ||
this.host = config.host || 'localhost'; | ||
this.port = config.port || 6379; | ||
this.redisConfig = config.redisConfig || {}; | ||
this.namespace = config.namespace; | ||
this.lazy = config.lazy || false; | ||
this.redisClient = redisManager.getClient(this.port, this.host, this.redisConfig); | ||
this.redisClient.on('error', this.emit.bind(this, 'error')); | ||
this.cache = {}; | ||
|
||
if (!this.lazy) { | ||
this.redisClient.hgetall(this.namespace, function(err, cache) { | ||
if (err) this.emit('error', err); | ||
if (cache) this.cache = cache; | ||
}.bind(this)); | ||
} | ||
} | ||
|
||
module.exports = cacheRedis; | ||
util.inherits(CacheRedis, EventEmitter); | ||
|
||
CacheRedis.prototype.close = function() { | ||
redisManager.freeClient(this.redisClient); | ||
}; | ||
|
||
CacheRedis.prototype.set = function(key, val, callback) { | ||
this.cache[key] = val; | ||
this.redisClient.hset(this.namespace, key, val, callback); | ||
}; | ||
|
||
CacheRedis.prototype.getLocal = function(key) { | ||
return this.cache[key]; | ||
}; | ||
|
||
CacheRedis.prototype.get = function(key, callback) { | ||
if (this.cache.hasOwnProperty(key)) return process.nextTick(callback.bind(this, null, this.cache[key])); | ||
this.redisClient.hget(this.namespace, key, function(err, val) { | ||
if (err) return callback(err); | ||
this.cache[key] = val || undefined; | ||
callback(null, this.cache[key]); | ||
}.bind(this)); | ||
}; | ||
|
||
CacheRedis.prototype.keysLocal = function() { | ||
return Object.keys(this.cache); | ||
}; | ||
|
||
CacheRedis.prototype.keys = function(callback) { | ||
this.redisClient.hkeys(this.namespace, callback); | ||
}; | ||
|
||
CacheRedis.prototype.valuesLocal = function() { | ||
return Object.keys(this.cache).map(function(key) { return this.cache[key]; }.bind(this)); | ||
}; | ||
|
||
CacheRedis.prototype.values = function(callback) { | ||
this.redisClient.hvals(this.namespace, callback); | ||
}; | ||
|
||
CacheRedis.prototype.has = function(key, callback) { | ||
if (this.cache.hasOwnProperty(key)) { | ||
process.nextTick(callback.bind(this, true)); | ||
} else { | ||
this.redisClient.hexists(this.namespace, key, function(err, result) { | ||
if (err) return callback(false); | ||
callback(!!result); // Cast to boolean | ||
}); | ||
} | ||
}; | ||
|
||
CacheRedis.prototype.hasLocal = function(key) { | ||
return this.cache.hasOwnProperty(key); | ||
}; | ||
|
||
// TODO: Add other ES6 Map methods: items, forEach, iterator, delete, clear, toString, and the property size | ||
|
||
module.exports = CacheRedis; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module.exports = function mutate(obj, key, val, scope) { | ||
return function(test) { | ||
var old = obj[key]; | ||
obj[key] = val; | ||
var boundTestDone = test.end.bind(test); | ||
test.end = function() { | ||
obj[key] = old; | ||
boundTestDone(); | ||
}; | ||
try { | ||
scope.apply(this, arguments); | ||
} catch(e) { | ||
obj[key] = old; | ||
throw e; | ||
} | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,246 @@ | ||
var CacheRedis = require('..'); | ||
var redisManager = require('redis-manager'); | ||
var mutate = require('../lib/mutate'); | ||
var test = require('tape'); | ||
|
||
var cacheRedis = require('../index.js'); | ||
var redisClient = redisManager.getClient(); | ||
|
||
test('cacheRedis is a function', function (assert) { | ||
assert.strictEqual(typeof cacheRedis, 'function'); | ||
test('clear', function(assert) { | ||
redisClient.flushall(function() { | ||
assert.end(); | ||
}); | ||
}); | ||
|
||
function createCache() { | ||
return new CacheRedis({ | ||
namespace: 'cacheTest' | ||
}); | ||
} | ||
|
||
test('initialize', function(assert) { | ||
assert.plan(5); | ||
var cache = createCache(); | ||
assert.equal(cache.host, 'localhost', 'got default host'); | ||
assert.equal(cache.port, 6379, 'got default port'); | ||
assert.equal(cache.namespace, 'cacheTest', 'got passed in cache'); | ||
assert.equal(cache.lazy, false, 'greedy by default'); | ||
assert.ok(cache.redisClient.flushall instanceof Function, 'correctly constructed a redis object'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
|
||
test('emits error if error during initialization', mutate(redisManager, 'getClient', function() { | ||
return { | ||
on: function() { }, | ||
hgetall: function(namespace, callback) { | ||
console.log(arguments); | ||
callback(new Error('initialization error')); | ||
} | ||
}; | ||
}, function(assert) { | ||
assert.plan(1); | ||
// Not possible to listen for events emitted during construction | ||
assert.throws(function() { | ||
createCache(); | ||
}, /initialization error/, 'got the mocked error'); | ||
assert.end(); | ||
})); | ||
|
||
test('set', function(assert) { | ||
assert.plan(3); | ||
var cache = createCache(); | ||
assert.doesNotThrow(function() { | ||
cache.set('foo', 'bar'); | ||
}, 'can set fire and forget'); | ||
cache.set('foo2', 'bar2', function() { | ||
redisClient.hget('cacheTest', 'foo2', function(err, val) { | ||
assert.ifError(err, 'does not fail to get from redis'); | ||
assert.equal(val, cache.cache.foo2, 'key written to redis and matches memory'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}); | ||
}); | ||
|
||
test('getLocal', function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
setTimeout(function() { | ||
var val = cache.getLocal('foo'); | ||
assert.equal(val, 'bar', 'got the expected value even from a separate constructor obj'); | ||
cache.close(); | ||
assert.end(); | ||
}, 50); | ||
}); | ||
|
||
test('getLocal non existent', function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
var val = cache.getLocal('nonexistent'); | ||
assert.equal(val, undefined, 'got the expected undefined value for a nonexistent key'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
|
||
test('get', function(assert) { | ||
assert.plan(2); | ||
var cache = createCache(); | ||
cache.get('foo2', function(err, val) { | ||
assert.ifError(err, 'should not get an error back'); | ||
assert.equal(val, 'bar2', 'got the expected value from a separate constructor obj'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}); | ||
|
||
test('get already cached value', function(assert) { | ||
assert.plan(2); | ||
var cache = createCache(); | ||
setTimeout(function() { | ||
cache.get('foo2', function(err, val) { | ||
assert.ifError(err, 'should not get an error back'); | ||
assert.equal(val, 'bar2', 'got the expected value from a separate constructor obj'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}, 50); | ||
}); | ||
|
||
test('getLazy', function(assert) { | ||
assert.plan(3); | ||
var cache = new CacheRedis({ | ||
namespace: 'cacheTest', | ||
lazy: true | ||
}); | ||
assert.ok(!cache.cache.foo2, 'key not yet in memory'); | ||
cache.get('foo2', function(err, val) { | ||
assert.ifError(err, 'should not get an error back'); | ||
assert.equal(val, 'bar2', 'got the expected value even though not originally in memory'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}); | ||
|
||
test('get non existent', function(assert) { | ||
assert.plan(2); | ||
var cache = createCache(); | ||
cache.get('nonexistent', function(err, result) { | ||
assert.ifError(err, 'got no error'); | ||
assert.equal(result, undefined, 'got no result'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}); | ||
|
||
test('get redis failure', mutate(redisClient, 'hget', function(namespace, key, callback) { | ||
callback(new Error('fake error')); | ||
}, function (assert) { | ||
assert.plan(2); | ||
var cache = new CacheRedis({ | ||
namespace: 'cacheTest', | ||
lazy: true | ||
}); | ||
redisManager.freeClient(cache.redisClient); | ||
cache.redisClient = redisClient; | ||
cache.get('foo2', function(err) { | ||
assert.ok(err instanceof Error, 'got an error back'); | ||
assert.equal(err.message, 'fake error', 'got the fake redisClient error back'); | ||
assert.end(); | ||
}); | ||
})); | ||
|
||
test('keysLocal', function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
setTimeout(function() { | ||
assert.deepEqual(cache.keysLocal().sort(), ['foo', 'foo2'], 'got both keys'); | ||
cache.close(); | ||
assert.end(); | ||
}, 50); | ||
}); | ||
|
||
test('keys', function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
cache.keys(function(err, keys) { | ||
assert.deepEqual(keys.sort(), ['foo', 'foo2'], 'got both keys'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}); | ||
|
||
test('valuesLocal', function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
setTimeout(function() { | ||
assert.deepEqual(cache.valuesLocal().sort(), ['bar', 'bar2'], 'got both values'); | ||
cache.close(); | ||
assert.end(); | ||
}, 50); | ||
}); | ||
|
||
test('values', function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
cache.values(function(err, values) { | ||
assert.deepEqual(values.sort(), ['bar', 'bar2'], 'got both values'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}); | ||
|
||
test('has', function(assert) { | ||
assert.plan(2); | ||
var cache = createCache(); | ||
cache.has('foo', function(has) { | ||
assert.equal(has, true, 'has that key'); | ||
cache.has('lolno', function(has) { | ||
assert.equal(has, false, 'does not have that key'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}); | ||
}); | ||
|
||
test('has already cached', function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
setTimeout(function() { | ||
cache.has('foo', function(has) { | ||
assert.equal(has, true, 'has that key'); | ||
cache.close(); | ||
assert.end(); | ||
}); | ||
}, 50); | ||
}); | ||
|
||
test('has returns false on error', mutate(redisClient, 'hexists', function(namespace, key, callback) { | ||
callback(new Error('Rar! This is an error of some sort!')); | ||
}, function(assert) { | ||
assert.plan(1); | ||
var cache = createCache(); | ||
redisManager.freeClient(cache.redisClient); | ||
cache.redisClient = redisClient; | ||
cache.has('foo', function(has) { | ||
assert.equal(has, false, 'returns false if having issues with redis'); | ||
assert.end(); | ||
}); | ||
})); | ||
|
||
test('hasLocal', function(assert) { | ||
assert.plan(2); | ||
var cache = createCache(); | ||
setTimeout(function() { | ||
assert.equal(cache.hasLocal('foo'), true, 'has that key'); | ||
assert.equal(cache.hasLocal('lolno'), false, 'does not have that key'); | ||
cache.close(); | ||
assert.end(); | ||
}, 50); | ||
}); | ||
|
||
test('clearAgain', function (assert) { | ||
redisClient.flushall(function() { | ||
redisManager.freeClient(redisClient); | ||
assert.end(); | ||
}); | ||
}); |