• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1var test = require('tap').test
2var lockFile = require('../lockfile.js')
3var path = require('path')
4var fs = require('fs')
5var touch = require('touch')
6
7// On Unix systems, it uses ctime by default for staleness checks, since it's
8// the most reliable.  However, because this test artificially sets some locks
9// to an earlier time to simulate staleness, we use mtime here.
10lockFile.filetime = 'mtime'
11
12test('setup', function (t) {
13  try { lockFile.unlockSync('basic-lock') } catch (er) {}
14  try { lockFile.unlockSync('sync-lock') } catch (er) {}
15  try { lockFile.unlockSync('never-forget') } catch (er) {}
16  try { lockFile.unlockSync('stale-lock') } catch (er) {}
17  try { lockFile.unlockSync('watch-lock') } catch (er) {}
18  try { lockFile.unlockSync('retry-lock') } catch (er) {}
19  try { lockFile.unlockSync('contentious-lock') } catch (er) {}
20  try { lockFile.unlockSync('stale-wait-lock') } catch (er) {}
21  try { lockFile.unlockSync('stale-windows-lock') } catch (er) {}
22  t.end()
23})
24
25test('lock contention', function (t) {
26  var gotlocks = 0;
27  var N = 200
28  var delay = 10
29  // allow for some time for each lock acquisition and release.
30  // note that raising N higher will mean that the overhead
31  // increases, because we're creating more and more watchers.
32  // irl, you should never have several hundred contenders for a
33  // single lock, so this situation is somewhat pathological.
34  var overhead = 200
35  var wait = N * overhead + delay
36
37  // first make it locked, so that everyone has to wait
38  lockFile.lock('contentious-lock', function(er, lock) {
39    t.ifError(er, 'acquiring starter')
40    if (er) throw er;
41    t.pass('acquired starter lock')
42    setTimeout(function() {
43      lockFile.unlock('contentious-lock', function (er) {
44        t.ifError(er, 'unlocking starter')
45        if (er) throw er
46        t.pass('unlocked starter')
47      })
48    }, delay)
49  })
50
51  for (var i=0; i < N; i++)
52    lockFile.lock('contentious-lock', { wait: wait }, function(er, lock) {
53      if (er) throw er;
54      lockFile.unlock('contentious-lock', function(er) {
55        if (er) throw er
56        gotlocks++
57        t.pass('locked and unlocked #' + gotlocks)
58        if (gotlocks === N) {
59          t.pass('got all locks')
60          t.end()
61        }
62      })
63    })
64})
65
66test('basic test', function (t) {
67  lockFile.check('basic-lock', function (er, locked) {
68    if (er) throw er
69    t.notOk(locked)
70    lockFile.lock('basic-lock', function (er) {
71      if (er) throw er
72      lockFile.lock('basic-lock', function (er) {
73        t.ok(er)
74        lockFile.check('basic-lock', function (er, locked) {
75          if (er) throw er
76          t.ok(locked)
77          lockFile.unlock('basic-lock', function (er) {
78            if (er) throw er
79            lockFile.check('basic-lock', function (er, locked) {
80              if (er) throw er
81              t.notOk(locked)
82              t.end()
83            })
84          })
85        })
86      })
87    })
88  })
89})
90
91test('sync test', function (t) {
92  var locked
93  locked = lockFile.checkSync('sync-lock')
94  t.notOk(locked)
95  lockFile.lockSync('sync-lock')
96  locked = lockFile.checkSync('sync-lock')
97  t.ok(locked)
98  lockFile.unlockSync('sync-lock')
99  locked = lockFile.checkSync('sync-lock')
100  t.notOk(locked)
101  t.end()
102})
103
104test('exit cleanup test', function (t) {
105  var child = require.resolve('./fixtures/child.js')
106  var node = process.execPath
107  var spawn = require('child_process').spawn
108  spawn(node, [child]).on('exit', function () {
109    setTimeout(function () {
110      var locked = lockFile.checkSync('never-forget')
111      t.notOk(locked)
112      t.end()
113    }, 100)
114  })
115})
116
117test('error exit cleanup test', function (t) {
118  var child = require.resolve('./fixtures/bad-child.js')
119  var node = process.execPath
120  var spawn = require('child_process').spawn
121  spawn(node, [child]).on('exit', function () {
122    setTimeout(function () {
123      var locked = lockFile.checkSync('never-forget')
124      t.notOk(locked)
125      t.end()
126    }, 100)
127  })
128})
129
130
131test('staleness test', function (t) {
132  lockFile.lock('stale-lock', function (er) {
133    if (er) throw er
134
135    // simulate 2s old
136    touch.sync('stale-lock', { time: new Date(Date.now() - 2000) })
137
138    var opts = { stale: 1 }
139    lockFile.check('stale-lock', opts, function (er, locked) {
140      if (er) throw er
141      t.notOk(locked)
142      lockFile.lock('stale-lock', opts, function (er) {
143        if (er) throw er
144        lockFile.unlock('stale-lock', function (er) {
145          if (er) throw er
146          t.end()
147        })
148      })
149    })
150  })
151})
152
153test('staleness sync test', function (t) {
154  var opts = { stale: 1 }
155  lockFile.lockSync('stale-lock')
156  // simulate 2s old
157  touch.sync('stale-lock', { time: new Date(Date.now() - 2000) })
158  var locked
159  locked = lockFile.checkSync('stale-lock', opts)
160  t.notOk(locked)
161  lockFile.lockSync('stale-lock', opts)
162  lockFile.unlockSync('stale-lock')
163  t.end()
164})
165
166test('retries', function (t) {
167  // next 5 opens will fail.
168  var opens = 5
169  fs._open = fs.open
170  fs.open = function (path, mode, cb) {
171    if (--opens === 0) {
172      fs.open = fs._open
173      return fs.open(path, mode, cb)
174    }
175    var er = new Error('bogus')
176    // to be, or not to be, that is the question.
177    er.code = opens % 2 ? 'EEXIST' : 'ENOENT'
178    process.nextTick(cb.bind(null, er))
179  }
180
181  lockFile.lock('retry-lock', { retries: opens }, function (er) {
182    if (er) throw er
183    t.equal(opens, 0)
184    lockFile.unlockSync('retry-lock')
185    t.end()
186  })
187})
188
189test('retryWait', function (t) {
190  // next 5 opens will fail.
191  var opens = 5
192  fs._open = fs.open
193  fs.open = function (path, mode, cb) {
194    if (--opens === 0) {
195      fs.open = fs._open
196      return fs.open(path, mode, cb)
197    }
198    var er = new Error('bogus')
199    // to be, or not to be, that is the question.
200    er.code = opens % 2 ? 'EEXIST' : 'ENOENT'
201    process.nextTick(cb.bind(null, er))
202  }
203
204  var opts = { retries: opens, retryWait: 100 }
205  lockFile.lock('retry-lock', opts, function (er) {
206    if (er) throw er
207    t.equal(opens, 0)
208    lockFile.unlockSync('retry-lock')
209    t.end()
210  })
211})
212
213test('retry sync', function (t) {
214  // next 5 opens will fail.
215  var opens = 5
216  fs._openSync = fs.openSync
217  fs.openSync = function (path, mode) {
218    if (--opens === 0) {
219      fs.openSync = fs._openSync
220      return fs.openSync(path, mode)
221    }
222    var er = new Error('bogus')
223    // to be, or not to be, that is the question.
224    er.code = opens % 2 ? 'EEXIST' : 'ENOENT'
225    throw er
226  }
227
228  var opts = { retries: opens }
229  lockFile.lockSync('retry-lock', opts)
230  t.equal(opens, 0)
231  lockFile.unlockSync('retry-lock')
232  t.end()
233})
234
235test('wait and stale together', function (t) {
236  // first locker.
237  var interval
238  lockFile.lock('stale-wait-lock', function(er) {
239    // keep refreshing the lock, so we keep it forever
240    interval = setInterval(function() {
241      touch.sync('stale-wait-lock')
242    }, 10)
243
244    // try to get another lock.  this must fail!
245    var opt = { stale: 1000, wait: 2000, pollInterval: 1000 }
246    lockFile.lock('stale-wait-lock', opt, function (er) {
247      if (!er)
248        t.fail('got second lock?  that unpossible!')
249      else
250        t.pass('second lock failed, as i have foreseen it')
251      clearInterval(interval)
252      t.end()
253    })
254  })
255})
256
257
258test('stale windows file tunneling test', function (t) {
259  // for windows only
260  // nt file system tunneling feature will make file creation time not updated
261  var opts = { stale: 1000 }
262  lockFile.lockSync('stale-windows-lock')
263  touch.sync('stale-windows-lock', { time: new Date(Date.now() - 3000) })
264
265  var locked
266  lockFile.unlockSync('stale-windows-lock')
267  lockFile.lockSync('stale-windows-lock', opts)
268  locked = lockFile.checkSync('stale-windows-lock', opts)
269  t.ok(locked, "should be locked and not stale")
270  lockFile.lock('stale-windows-lock', opts, function (er) {
271    if (!er)
272      t.fail('got second lock?  impossible, windows file tunneling problem!')
273    else
274      t.pass('second lock failed, windows file tunneling problem fixed')
275    t.end()
276  })
277})
278
279
280test('cleanup', function (t) {
281  try { lockFile.unlockSync('basic-lock') } catch (er) {}
282  try { lockFile.unlockSync('sync-lock') } catch (er) {}
283  try { lockFile.unlockSync('never-forget') } catch (er) {}
284  try { lockFile.unlockSync('stale-lock') } catch (er) {}
285  try { lockFile.unlockSync('watch-lock') } catch (er) {}
286  try { lockFile.unlockSync('retry-lock') } catch (er) {}
287  try { lockFile.unlockSync('contentious-lock') } catch (er) {}
288  try { lockFile.unlockSync('stale-wait-lock') } catch (er) {}
289  try { lockFile.unlockSync('stale-windows-lock') } catch (er) {}
290  t.end()
291})
292
293