var test = require('tap').test var lockFile = require('../lockfile.js') var path = require('path') var fs = require('fs') var touch = require('touch') test('setup', function (t) { try { lockFile.unlockSync('basic-lock') } catch (er) {} try { lockFile.unlockSync('sync-lock') } catch (er) {} try { lockFile.unlockSync('never-forget') } catch (er) {} try { lockFile.unlockSync('stale-lock') } catch (er) {} try { lockFile.unlockSync('watch-lock') } catch (er) {} try { lockFile.unlockSync('retry-lock') } catch (er) {} try { lockFile.unlockSync('contentious-lock') } catch (er) {} try { lockFile.unlockSync('stale-wait-lock') } catch (er) {} t.end() }) test('lock contention', function (t) { var gotlocks = 0; var N = 200 var delay = 10 // allow for some time for each lock acquisition and release. // note that raising N higher will mean that the overhead // increases, because we're creating more and more watchers. // irl, you should never have several hundred contenders for a // single lock, so this situation is somewhat pathological. var overhead = 200 var wait = N * overhead + delay // first make it locked, so that everyone has to wait lockFile.lock('contentious-lock', function(er, lock) { t.ifError(er, 'acquiring starter') if (er) throw er; t.pass('acquired starter lock') setTimeout(function() { lockFile.unlock('contentious-lock', function (er) { t.ifError(er, 'unlocking starter') if (er) throw er t.pass('unlocked starter') }) }, delay) }) for (var i=0; i < N; i++) lockFile.lock('contentious-lock', { wait: wait }, function(er, lock) { if (er) throw er; lockFile.unlock('contentious-lock', function(er) { if (er) throw er gotlocks++ t.pass('locked and unlocked #' + gotlocks) if (gotlocks === N) { t.pass('got all locks') t.end() } }) }) }) test('basic test', function (t) { lockFile.check('basic-lock', function (er, locked) { if (er) throw er t.notOk(locked) lockFile.lock('basic-lock', function (er) { if (er) throw er lockFile.lock('basic-lock', function (er) { t.ok(er) lockFile.check('basic-lock', function (er, locked) { if (er) throw er t.ok(locked) lockFile.unlock('basic-lock', function (er) { if (er) throw er lockFile.check('basic-lock', function (er, locked) { if (er) throw er t.notOk(locked) t.end() }) }) }) }) }) }) }) test('sync test', function (t) { var locked locked = lockFile.checkSync('sync-lock') t.notOk(locked) lockFile.lockSync('sync-lock') locked = lockFile.checkSync('sync-lock') t.ok(locked) lockFile.unlockSync('sync-lock') locked = lockFile.checkSync('sync-lock') t.notOk(locked) t.end() }) test('exit cleanup test', function (t) { var child = require.resolve('./fixtures/child.js') var node = process.execPath var spawn = require('child_process').spawn spawn(node, [child]).on('exit', function () { setTimeout(function () { var locked = lockFile.checkSync('never-forget') t.notOk(locked) t.end() }, 100) }) }) test('error exit cleanup test', function (t) { var child = require.resolve('./fixtures/bad-child.js') var node = process.execPath var spawn = require('child_process').spawn spawn(node, [child]).on('exit', function () { setTimeout(function () { var locked = lockFile.checkSync('never-forget') t.notOk(locked) t.end() }, 100) }) }) test('staleness test', function (t) { lockFile.lock('stale-lock', function (er) { if (er) throw er var opts = { stale: 1 } setTimeout(next, 1000) function next () { lockFile.check('stale-lock', opts, function (er, locked) { if (er) throw er t.notOk(locked) lockFile.lock('stale-lock', opts, function (er) { if (er) throw er lockFile.unlock('stale-lock', function (er) { if (er) throw er t.end() }) }) }) } }) }) test('staleness sync test', function (t) { var opts = { stale: 1 } lockFile.lockSync('stale-lock') setTimeout(next, 1000) function next () { var locked locked = lockFile.checkSync('stale-lock', opts) t.notOk(locked) lockFile.lockSync('stale-lock', opts) lockFile.unlockSync('stale-lock') t.end() } }) test('watch test', function (t) { var opts = { wait: 100 } var fdx lockFile.lock('watch-lock', function (er, fd1) { if (er) throw er setTimeout(unlock, 10) function unlock () { console.error('unlocking it') lockFile.unlockSync('watch-lock') // open another file, so the fd gets reused // so we can know that it actually re-opened it fresh, // rather than just getting the same lock as before. fdx = fs.openSync('x', 'w') fdy = fs.openSync('x', 'w') } // should have gotten a new fd lockFile.lock('watch-lock', opts, function (er, fd2) { if (er) throw er t.notEqual(fd1, fd2) fs.closeSync(fdx) fs.closeSync(fdy) fs.unlinkSync('x') lockFile.unlockSync('watch-lock') t.end() }) }) }) test('retries', function (t) { // next 5 opens will fail. var opens = 5 fs._open = fs.open fs.open = function (path, mode, cb) { if (--opens === 0) { fs.open = fs._open return fs.open(path, mode, cb) } var er = new Error('bogus') // to be, or not to be, that is the question. er.code = opens % 2 ? 'EEXIST' : 'ENOENT' process.nextTick(cb.bind(null, er)) } lockFile.lock('retry-lock', { retries: opens }, function (er, fd) { if (er) throw er t.equal(opens, 0) t.ok(fd) lockFile.unlockSync('retry-lock') t.end() }) }) test('retryWait', function (t) { // next 5 opens will fail. var opens = 5 fs._open = fs.open fs.open = function (path, mode, cb) { if (--opens === 0) { fs.open = fs._open return fs.open(path, mode, cb) } var er = new Error('bogus') // to be, or not to be, that is the question. er.code = opens % 2 ? 'EEXIST' : 'ENOENT' process.nextTick(cb.bind(null, er)) } var opts = { retries: opens, retryWait: 100 } lockFile.lock('retry-lock', opts, function (er, fd) { if (er) throw er t.equal(opens, 0) t.ok(fd) lockFile.unlockSync('retry-lock') t.end() }) }) test('retry sync', function (t) { // next 5 opens will fail. var opens = 5 fs._openSync = fs.openSync fs.openSync = function (path, mode) { if (--opens === 0) { fs.openSync = fs._openSync return fs.openSync(path, mode) } var er = new Error('bogus') // to be, or not to be, that is the question. er.code = opens % 2 ? 'EEXIST' : 'ENOENT' throw er } var opts = { retries: opens } lockFile.lockSync('retry-lock', opts) t.equal(opens, 0) lockFile.unlockSync('retry-lock') t.end() }) test('wait and stale together', function (t) { // first locker. var interval lockFile.lock('stale-wait-lock', function(er) { // keep refreshing the lock, so we keep it forever interval = setInterval(function() { touch.sync('stale-wait-lock') }, 10) // try to get another lock. this must fail! var opt = { stale: 1000, wait: 2000 } lockFile.lock('stale-wait-lock', opt, function (er) { if (!er) t.fail('got second lock? that unpossible!') else t.pass('second lock failed, as i have foreseen it') clearInterval(interval) t.end() }) }) }) test('cleanup', function (t) { try { lockFile.unlockSync('basic-lock') } catch (er) {} try { lockFile.unlockSync('sync-lock') } catch (er) {} try { lockFile.unlockSync('never-forget') } catch (er) {} try { lockFile.unlockSync('stale-lock') } catch (er) {} try { lockFile.unlockSync('watch-lock') } catch (er) {} try { lockFile.unlockSync('retry-lock') } catch (er) {} try { lockFile.unlockSync('contentious-lock') } catch (er) {} try { lockFile.unlockSync('stale-wait-lock') } catch (er) {} t.end() })