UNPKG

lru-cache-for-clusters-as-promised

Version:

LRU Cache that is safe for clusters, based on `lru-cache`. Save memory by only caching items on the main thread via a promisified interface.

801 lines (783 loc) 23.9 kB
const config = require('./test-config'); const cluster = require('cluster'); const { parse, stringify } = require('flatted'); const should = require('should'); const LRUCacheForClustersAsPromised = require('../../lru-cache-for-clusters-as-promised'); const LRUCache = require('lru-cache'); const member = cluster.isWorker ? 'worker' : 'master'; // create a default new LRUCacheForClustersAsPromised(); /** * Test class definitions for clusterd and non-clustered environments * @param {LRUCacheForClustersAsPromised} cache The cache that the test should be run against. * @return {void} Node style callback */ function TestUtils(cache) { const object = { foo: 'bar barbarbar barbarbar barbarbar barbarbar barbarbar barbarbar barbarbar barbarbar barbar', }; const pairs = { foo: 'bar', bizz: 'buzz', obj: { hi: 'im an object', }, }; const keys = Object.keys(pairs); return { clusterTests: { hi: 'not respond to messages that are from somewhere else', timeout: 'timeout', reject: 'timeout with reject', }, tests: { executeSetGet: 'try to call set via the execute() option', executeFail: 'execute fail', getLruCachesOnMaster: 'getLruCaches to return the underlying LRUCaches from master, throw error on worker', getCache: 'get underlying LRUCache for promisified version', mSet: 'mSet values', mSetNull: 'mSet null pairs', mGet: 'mGet values', mGetNull: 'mGet with null keys', mGetAndSetObjects: 'mGetObjects and mSetObjects', mDel: 'mGet keys', mDelNull: 'mDel with null keys', objects: 'get and set objects', null_objects: 'null objects should be ok', undefined_objects: 'undefined objects should be ok', circular_objects: 'circular objects should be ok', miss_undefined: 'missing objects should return undefined', pruneJob: 'prune cache using cron job', pruneJob2: 'prune cache using cron job, longer than test', set: 'set(key, value)', get: 'get(key)', del: 'del(key)', incr: 'incr(key) - increment value by 1', incr2: 'incr(key, 2) - increment value by 2', decr: 'decr(key) - decrement value by 1', decr2: 'decr(key, 2) - decrement value by 2', peek: 'peek(key) - get a cache value but do not update access time for LRU', has: 'has(key) - check if a key exists', length: 'length()', itemCount: 'itemCount()', reset: 'reset()', keys: 'keys()', values: 'values()', prune: 'prune()', dump: 'dump()', addFour: 'add four keys and have the first fall out', addFourAccessOne: 'add four keys and then access the first so the second falls out', getMax: 'max()', getMaxAge: 'maxAge()', getStale: 'stale()', getAllowStale: 'allowStale()', setMax: 'max(10)', setMaxAge: 'maxAge(10)', setStale: 'stale(true)', setAllowStale: 'allowStale(true)', properties: 'update cache properties', getInstance: 'get an instance asynchronously, ensures cache has been created on the server', }, executeSetGet: async (cb) => { try { await cache.execute('set', 1, 'execute'); const value = await cache.execute('get', 1); should(value).equal('execute'); cb(null, true); } catch (err) { cb(err); } }, executeFail: async (cb) => { try { try { await cache.execute('borked', 1, 'execute'); } catch (err) { should(err.message).equal( 'LRUCache.borked() is not a valid function' ); } cb(null, true); } catch (err) { cb(err); } }, getLruCachesOnMaster: async (cb) => { try { try { const yo = 'yo yo yo'; // get the default cache and set the value using a promise const defCache = new LRUCacheForClustersAsPromised(); await defCache.set(1, yo); // get all the caches and check the default namespace const caches = LRUCacheForClustersAsPromised.getAllCaches(); should(typeof caches.default).not.equal('undefined'); should(caches.default instanceof LRUCache).equal(true); should(caches.default.allowStale).equal(false); should(caches.default.maxAge).equal(0); // get the value we set synchronously const value = caches.default.get(1); should(value).equal(yo); } catch (err) { if (!cluster.isWorker) { throw err; } should(err.message).containEql('LRUCacheForClustersAsPromised'); } cb(null, true); } catch (err) { cb(err); } }, getCache: async (cb) => { try { try { const foo = 'foo foo foo'; const defCache = new LRUCacheForClustersAsPromised(); await defCache.set(1, foo); const cache = defCache.getCache(); should(cache.get(1)).equal(foo); } catch (err) { if (!cluster.isWorker) { throw err; } } cb(null, true); } catch (err) { cb(err); } }, mSet: async (cb) => { try { await cache.mSet(pairs); const value = await cache.get(keys[0]); should(value).equal(pairs[keys[0]]); cb(null, true); } catch (err) { cb(err); } }, mSetNull: async (cb) => { try { await cache.mSet(null); await cache.mSet('string'); await cache.mSet(['array']); cb(null, true); } catch (err) { cb(err); } }, mGet: async (cb) => { try { await cache.mSet(pairs); const values = await cache.mGet(keys); should(typeof values).not.equal('undefined'); should(values.bizz).equal(pairs.bizz); should(values.foo).equal(pairs.foo); cb(null, true); } catch (err) { cb(err); } }, mGetAndSetObjects: async (cb) => { try { await cache.mSetObjects(pairs); const values = await cache.mGetObjects(keys); should(values.bizz).deepEqual(pairs.bizz); should(values.foo).deepEqual(pairs.foo); cb(null, true); } catch (err) { cb(err); } }, mGetNull: async (cb) => { try { let values = await cache.mGet('string'); should(values).deepEqual({}); values = await cache.mGet(null); should(values).deepEqual({}); cb(null, true); } catch (err) { cb(err); } }, mDel: async (cb) => { try { await cache.mSet(pairs); await cache.mDel(keys); const value = await cache.get(keys[0]); should(typeof value).equal('undefined'); cb(null, true); } catch (err) { cb(err); } }, mDelNull: async (cb) => { try { await cache.mSet(pairs); await cache.mDel(null); const value = await cache.get(keys[0]); should(value).equal(pairs[keys[0]]); cb(null, true); } catch (err) { cb(err); } }, objects: async (cb) => { try { await cache.setObject(1, object); const obj = await cache.getObject(1); should(obj).not.equal(null); should(obj.foo).equal(object.foo); cb(null, true); } catch (err) { cb(err); } }, undefined_objects: async (cb) => { try { let object; await cache.setObject(1, object); const obj = await cache.getObject(1); should(typeof obj).equal('undefined'); cb(null, true); } catch (err) { cb(err); } }, null_objects: async (cb) => { try { let object = null; await cache.setObject(1, object); const obj = await cache.getObject(1); should(obj).equal(null); cb(null, true); } catch (err) { cb(err); } }, circular_objects: async (cb) => { try { // this cache uses the flatted parse and stringify const cacheCircular = new LRUCacheForClustersAsPromised({ namespace: 'circular-cache', max: 3, parse, stringify, }); // create a circular dependency const a = { b: null }; const b = { a }; b.a.b = b; // see if we can set and then extract the circular object await cacheCircular.setObject(1, a); const obj = await cacheCircular.getObject(1); should(obj).deepEqual(a); should(obj.b).deepEqual(b); cb(null, true); } catch (err) { cb(err); } }, miss_undefined: async (cb) => { try { const obj = await cache.getObject(1); should(typeof obj).equal('undefined'); cb(null, true); } catch (err) { cb(err); } }, hi: async (cb) => { try { let responded = false; const callback = (response) => { if (!responded) { responded = true; should(response).equal('hello'); cb(null, true); } }; process.on('message', (response) => callback && callback(response)); process.send('hi'); } catch (err) { cb(err); } }, timeout: async (cb) => { try { const cacheBad = new LRUCacheForClustersAsPromised({ max: 1, stale: false, timeout: 1, namespace: `bad-cache-resolve-${member}`, }); let large = '1234567890'; for (let i = 0; i < 17; i += 1) { large += large; } const result = await cacheBad.get(`bad-cache-key-${large}`); cb(null, result); } catch (err) { cb(err); } }, reject: async (cb) => { try { const cacheBad = new LRUCacheForClustersAsPromised({ max: 2, stale: false, timeout: 1, failsafe: 'reject', namespace: `bad-cache-reject-${member}`, }); let large = '1234567890'; for (let i = 0; i < 17; i += 1) { large += large; } await cacheBad.get(`bad-cache-key-${large}`); cb('fail'); } catch (err) { cb(null, true); } }, pruneJob: async (cb) => { try { const namespace = `pruned-cache-${member}-${Math.random()}`; const prunedCache = new LRUCacheForClustersAsPromised({ max: 10, stale: true, maxAge: 100, namespace, prune: '*/1 * * * * *', }); // maybe delay the start to sync with cron const now = new Date(); const delay = now.getMilliseconds() < 800 ? 0 : 1000 - now.getMilliseconds() + 10; setTimeout(async () => { await prunedCache.set(config.args.one, config.args.one, 200); await prunedCache.set(config.args.two, config.args.two, 1200); const itemCount = await prunedCache.itemCount(); // we should see 2 items in the cache should(itemCount).equal(2); // check again in 1100 ms setTimeout(async () => { // one of the items should have been removed based on the expiration const itemCount2 = await prunedCache.itemCount(); try { should(itemCount2).equal(1); new LRUCacheForClustersAsPromised({ namespace, prune: false, }); return cb(null, true); } catch (err) { return cb(err); } }, 1100); }, delay); } catch (err) { cb(err); } }, pruneJob2: async (cb) => { try { const namespace = `pruned-cache-${member}-2-${Math.random()}`; // create it with 1 sec pruning new LRUCacheForClustersAsPromised({ namespace, prune: '*/1 * * * * *', }); // update it to run every 10 secs const prunedCache = new LRUCacheForClustersAsPromised({ namespace, prune: '*/5 * * * * *', }); // maybe delay the start to sync with cron const now = new Date(); const delay = now.getSeconds() % 5 < 4 ? 0 : 1000 - now.getMilliseconds() + 10; setTimeout(async () => { await prunedCache.set(config.args.one, config.args.one, 200); await prunedCache.set(config.args.two, config.args.two, 1200); const itemCount = await prunedCache.itemCount(); // we should see 2 items in the cache should(itemCount).equal(2); // check again in 1100 ms setTimeout(async () => { // both items should be there after they are expired const itemCount2 = await prunedCache.itemCount(); try { should(itemCount2).equal(2); // disable prune job await LRUCacheForClustersAsPromised.getInstance({ namespace, prune: false, }); return cb(null, true); } catch (err) { return cb(err); } }, 1000); }, delay); } catch (err) { cb(err); } }, set: async (cb) => { try { const result = await cache.set(config.args.one, config.args.one); cb(null, result); } catch (err) { cb(err); } }, get: async (cb) => { try { await cache.set(config.args.one, config.args.one); const result = await cache.get(config.args.one); should(result).equal(config.args.one); cb(null, result); } catch (err) { cb(err); } }, del: async (cb) => { try { await cache.set(config.args.one, config.args.one); await cache.del(config.args.one); const result = await cache.get(config.args.one); should(typeof result).equal('undefined'); cb(null, result); } catch (err) { cb(err); } }, incr: async (cb) => { try { const value = await cache.incr(config.args.one); should(value).eql(1); const value2 = await cache.incr(config.args.one); should(value2).eql(2); cb(null, true); } catch (err) { cb(err); } }, incr2: async (cb) => { try { const amount = 2; const value = await cache.incr(config.args.one, amount); should(value).eql(2); const value2 = await cache.incr(config.args.one, amount); should(value2).eql(4); cb(null, true); } catch (err) { cb(err); } }, decr: async (cb) => { try { const value = await cache.decr(config.args.one); should(value).eql(-1); const value2 = await cache.decr(config.args.one); should(value2).eql(-2); cb(null, true); } catch (err) { cb(err); } }, decr2: async (cb) => { try { const amount = 2; const value = await cache.decr(config.args.one, amount); should(value).eql(-2); const value2 = await cache.decr(config.args.one, amount); should(value2).eql(-4); cb(null, true); } catch (err) { cb(err); } }, peek: async (cb) => { try { await cache.set(config.args.one, config.args.one); await cache.set(config.args.two, config.args.two); await cache.set(config.args.three, config.args.three); const result = await cache.peek(config.args.one); should(result).equal(config.args.one); await cache.set(config.args.four, config.args.four); const result2 = await cache.get(config.args.one); should(typeof result2).equal('undefined'); cb(null, true); } catch (err) { cb(err); } }, has: async (cb) => { try { await cache.set(config.args.one, config.args.one); const has = await cache.has(config.args.one); should(has).equal(true); cb(null, true); } catch (err) { cb(err); } }, length: async (cb) => { try { await cache.set(config.args.two, config.args.two); await cache.set(config.args.three, config.args.three); const length = await cache.length(); should(length).equal(2); cb(null, true); } catch (err) { cb(err); } }, itemCount: async (cb) => { try { await cache.set(config.args.one, config.args.one); const itemCount = await cache.itemCount(); should(itemCount).equal(1); cb(null, true); } catch (err) { cb(err); } }, reset: async (cb) => { try { await cache.set(config.args.one, config.args.one); const result = await cache.get(config.args.one); should(typeof result).equal('string'); await cache.reset(); const result2 = await cache.get(config.args.one); should(typeof result2).equal('undefined'); cb(null, true); } catch (err) { cb(err); } }, keys: async (cb) => { try { const result = await cache.set(config.args.one, config.args.one); should(result).equal(true); const keys = await cache.keys(); should(keys.length).equal(1); should(keys[0]).equal(config.args.one); cb(null, true); } catch (err) { cb(err); } }, values: async (cb) => { try { await cache.set(config.args.two, config.args.two); const values = await cache.values(); should(values).deepEqual([config.args.two]); cb(null, true); } catch (err) { cb(err); } }, prune: async (cb) => { try { await cache.set(config.args.one, config.args.one); await cache.prune(); const itemCount = await cache.itemCount(); should(itemCount).equal(1); cb(null, true); } catch (err) { cb(err); } }, dump: async (cb) => { try { await cache.set(config.args.one, config.args.two); const dump = await cache.dump(); should(dump[0].k).equal(config.args.one); should(dump[0].v).equal(config.args.two); cb(null, true); } catch (err) { cb(err); } }, getMax: async (cb) => { try { const max = await cache.max(); should(max).equal(3); cb(null, true); } catch (err) { cb(err); } }, getMaxAge: async (cb) => { try { await cache.maxAge(20); const maxAge = await cache.maxAge(); should(maxAge).equal(20); cb(null, true); } catch (err) { cb(err); } }, getStale: async (cb) => { try { const stale = await cache.stale(); should(stale).equal(false); cb(null, true); } catch (err) { cb(err); } }, getAllowStale: async (cb) => { try { const stale = await cache.allowStale(); should(stale).equal(false); cb(null, true); } catch (err) { cb(err); } }, setMax: async (cb) => { try { const max = await cache.max(10000); should(max).equal(10000); cb(null, true); } catch (err) { cb(err); } }, setMaxAge: async (cb) => { try { const maxAge = await cache.maxAge(10); should(maxAge).equal(10); cb(null, true); } catch (err) { cb(err); } }, setStale: async (cb) => { try { const stale = await cache.stale(true); should(stale).equal(true); cb(null, true); } catch (err) { cb(err); } }, setAllowStale: async (cb) => { try { const stale = await cache.allowStale(true); should(stale).equal(true); cb(null, true); } catch (err) { cb(err); } }, properties: async (cb) => { try { const propsCache = new LRUCacheForClustersAsPromised({ namespace: 'props-cache', max: 1, maxAge: 100000, stale: true, }); should(await propsCache.allowStale()).equal(true); should(await propsCache.max()).equal(1); should(await propsCache.maxAge()).equal(100000); const propsCache2 = new LRUCacheForClustersAsPromised({ namespace: 'props-cache', max: 10101, stale: false, }); should(await propsCache2.allowStale()).equal(false); should(await propsCache2.max()).equal(10101); should(await propsCache2.maxAge()).equal(100000); const propsCache3 = new LRUCacheForClustersAsPromised({ namespace: 'props-cache', maxAge: 1000, }); should(await propsCache3.allowStale()).equal(false); should(await propsCache3.max()).equal(10101); should(await propsCache3.maxAge()).equal(1000); cb(null, true); } catch (err) { cb(err); } }, getInstance: async (cb) => { try { const propsCache = await LRUCacheForClustersAsPromised.getInstance({ namespace: 'props-cache', max: 1, maxAge: 100000, stale: true, }); should(await propsCache.allowStale()).equal(true); should(await propsCache.max()).equal(1); should(await propsCache.maxAge()).equal(100000); const propsCache2 = await LRUCacheForClustersAsPromised.getInstance({ namespace: 'props-cache', max: 10101, stale: false, }); should(await propsCache2.allowStale()).equal(false); should(await propsCache2.max()).equal(10101); should(await propsCache2.maxAge()).equal(100000); const propsCache3 = await LRUCacheForClustersAsPromised.getInstance({ namespace: 'props-cache', maxAge: 1000, }); should(await propsCache3.allowStale()).equal(false); should(await propsCache3.max()).equal(10101); should(await propsCache3.maxAge()).equal(1000); cb(null, true); } catch (err) { cb(err); } }, addFour: async (cb) => { try { const value = await cache.set(config.args.one, config.args.one); should(value).equal(true); await cache.set(config.args.two, config.args.two); await cache.set(config.args.three, config.args.three); await cache.set(config.args.four, config.args.four); const result = await cache.get(config.args.one); should(typeof result).equal('undefined'); const result2 = await cache.get(config.args.four); should(result2).equal(config.args.four); cb(null, true); } catch (err) { cb(err); } }, addFourAccessOne: async (cb) => { try { const value = await cache.set(config.args.one, config.args.one); should(value).equal(true); const value2 = await cache.set(config.args.two, config.args.two); should(value2).equal(true); const value3 = await cache.set(config.args.three, config.args.three); should(value3).equal(true); const value4 = await cache.get(config.args.one); should(value4).equal(config.args.one); const value5 = await cache.set(config.args.four, config.args.four); should(value5).equal(true); const result = await cache.get(config.args.one); should(result).equal(config.args.one); cb(null, true); } catch (err) { cb(err); } }, }; } module.exports = TestUtils;