UNPKG

apostrophe

Version:
220 lines (174 loc) • 5.85 kB
const t = require('../test-lib/test.js'); const assert = require('assert'); const Promise = require('bluebird'); describe('Locks', function() { let apos; this.timeout(t.timeout); after(function() { return t.destroy(apos); }); it('should be a property of the apos object', async function() { this.timeout(t.timeout); this.slow(2000); apos = await t.create({ root: module, modules: { // Make some subclasses of the locks module. NORMALLY A BAD IDEA. But // we're doing it to deliberately force them to contend with each other, // rather than just throwing an error saying "hey you have this lock // now" '@apostrophecms/lock-1': { extend: '@apostrophecms/lock', options: { alias: 'locks1' } }, '@apostrophecms/lock-2': { extend: '@apostrophecms/lock', options: { alias: 'locks2' } }, '@apostrophecms/lock-3': { extend: '@apostrophecms/lock', options: { alias: 'locks3' } } } }); assert(apos.modules['@apostrophecms/lock']); assert(apos.modules['@apostrophecms/lock-1']); assert(apos.modules['@apostrophecms/lock-2']); assert(apos.modules['@apostrophecms/lock-3']); }); it('cleanup', async function() { await apos.lock.db.deleteMany({}); }); it('should allow a single lock without contention uneventfully', async function() { await apos.lock.lock('test'); await apos.lock.unlock('test'); }); it('should allow two differently-named locks uneventfully', async function() { await apos.lock.lock('test1'); await apos.lock.lock('test2'); await apos.lock.unlock('test1'); await apos.lock.unlock('test2'); }); it('should flunk a second lock by the same module with waitForSelf: false', async function() { await apos.lock.lock('test'); try { await apos.lock.lock('test', { waitForSelf: false }); assert(false); } catch (e) { assert(e); } await apos.lock.unlock('test'); try { await apos.lock.unlock('test'); assert(false); } catch (e) { assert(e); } }); it('four parallel lock calls via the different modules should all succeed but not simultaneously', async function() { const one = apos.modules['@apostrophecms/lock']; const two = apos.modules['@apostrophecms/lock-1']; const three = apos.modules['@apostrophecms/lock-2']; const four = apos.modules['@apostrophecms/lock-3']; let active = 0; let successful = 0; await Promise.all([ attempt(one), attempt(two), attempt(three), attempt(four) ]); assert(successful === 4); async function attempt(locks) { await apos.lock.lock('test'); active++; assert(active === 1); return release(); async function release() { // We have to decrement this before we start the call to // apos.lock.unlock because otherwise the callback for one of our // peers' insert attempts may succeed before the callback for // remove, leading to a false positive for test failure. -Tom active--; await Promise.delay(75 + Math.random() * 50); await apos.lock.unlock('test'); successful++; return null; } } }); it('four parallel lock calls via the different modules should all succeed but not simultaneously, even when the idleTimeout is short', async function() { const one = apos.modules['@apostrophecms/lock']; const two = apos.modules['@apostrophecms/lock-1']; const three = apos.modules['@apostrophecms/lock-2']; const four = apos.modules['@apostrophecms/lock-3']; let active = 0; let successful = 0; await Promise.all([ attempt(one), attempt(two), attempt(three), attempt(four) ]); assert(successful === 4); async function attempt(locks) { await apos.lock.lock('test', { idleTimeout: 50 }); active++; assert(active === 1); await release(); async function release() { // We have to decrement this before we start the call to // apos.lock.unlock because otherwise the callback for one of our // peers' insert attempts may succeed before the callback for // remove, leading to a false positive for test failure. -Tom active--; await Promise.delay(75 + Math.random() * 50); await apos.lock.unlock('test'); successful++; return null; } } }); it('withLock method should run a function inside a lock', async function() { const result = await apos.lock.withLock('test-lock', async () => { await Promise.delay(50); return 'result'; }); assert(result === 'result'); }); it('withLock method should be able to run again (lock released)', async function() { const result = await apos.lock.withLock('test-lock', async () => { await Promise.delay(50); return 'result'; }); assert(result === 'result'); }); it('withLock method should hold the lock (cannot relock within fn)', async function() { return apos.lock.withLock('test-lock', async () => { await Promise.delay(50); try { await apos.lock.lock('test-lock', { waitForSelf: false }); assert(false); } catch (e) { assert(e); } }); }); it('Second lock should wait for release of first one', async function() { let timedOut = false; await apos.lock.lock('test-lock'); setTimeout(async () => { await apos.lock.unlock('test-lock'); timedOut = true; }); await apos.lock.lock('test-lock'); assert(timedOut); await apos.lock.unlock('test-lock'); }); });