LockManagerTests.coffee 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/LockManager.js"
  6. SandboxedModule = require('sandboxed-module')
  7. describe "LockManager", ->
  8. beforeEach ->
  9. @LockManager = SandboxedModule.require modulePath, requires:
  10. "redis":
  11. createClient: () => @rclient =
  12. auth: sinon.stub()
  13. "settings-sharelatex": @Settings = {}
  14. @key = "lock-key"
  15. @callback = sinon.stub()
  16. describe "checkLock", ->
  17. describe "when the lock is taken", ->
  18. beforeEach ->
  19. @rclient.exists = sinon.stub().callsArgWith(1, null, "1")
  20. @LockManager.checkLock @key, @callback
  21. it "should check the lock in redis", ->
  22. @rclient.exists
  23. .calledWith(@key)
  24. .should.equal true
  25. it "should return the callback with false", ->
  26. @callback.calledWith(null, false).should.equal true
  27. describe "when the lock is free", ->
  28. beforeEach ->
  29. @rclient.exists = sinon.stub().callsArgWith(1, null, "0")
  30. @LockManager.checkLock @key, @callback
  31. it "should return the callback with true", ->
  32. @callback.calledWith(null, true).should.equal true
  33. describe "tryLock", ->
  34. describe "when the lock is taken", ->
  35. beforeEach ->
  36. @rclient.set = sinon.stub().callsArgWith(5, null, null)
  37. @LockManager.tryLock @key, @callback
  38. it "should check the lock in redis", ->
  39. @rclient.set
  40. .calledWith(@key, "locked", "EX", @LockManager.LOCK_TTL, "NX")
  41. .should.equal true
  42. it "should return the callback with false", ->
  43. @callback.calledWith(null, false).should.equal true
  44. describe "when the lock is free", ->
  45. beforeEach ->
  46. @rclient.set = sinon.stub().callsArgWith(5, null, "OK")
  47. @LockManager.tryLock @key, @callback
  48. it "should return the callback with true", ->
  49. @callback.calledWith(null, true).should.equal true
  50. describe "deleteLock", ->
  51. beforeEach ->
  52. beforeEach ->
  53. @rclient.del = sinon.stub().callsArg(1)
  54. @LockManager.deleteLock @key, @callback
  55. it "should delete the lock in redis", ->
  56. @rclient.del
  57. .calledWith(key)
  58. .should.equal true
  59. it "should call the callback", ->
  60. @callback.called.should.equal true
  61. describe "getLock", ->
  62. describe "when the lock is not taken", ->
  63. beforeEach (done) ->
  64. @LockManager.tryLock = sinon.stub().callsArgWith(1, null, true)
  65. @LockManager.getLock @key, (args...) =>
  66. @callback(args...)
  67. done()
  68. it "should try to get the lock", ->
  69. @LockManager.tryLock
  70. .calledWith(@key)
  71. .should.equal true
  72. it "should only need to try once", ->
  73. @LockManager.tryLock.callCount.should.equal 1
  74. it "should return the callback", ->
  75. @callback.calledWith(null).should.equal true
  76. describe "when the lock is initially set", ->
  77. beforeEach (done) ->
  78. startTime = Date.now()
  79. @LockManager.LOCK_TEST_INTERVAL = 5
  80. @LockManager.tryLock = (doc_id, callback = (error, isFree) ->) ->
  81. if Date.now() - startTime < 20
  82. callback null, false
  83. else
  84. callback null, true
  85. sinon.spy @LockManager, "tryLock"
  86. @LockManager.getLock @key, (args...) =>
  87. @callback(args...)
  88. done()
  89. it "should call tryLock multiple times until free", ->
  90. (@LockManager.tryLock.callCount > 1).should.equal true
  91. it "should return the callback", ->
  92. @callback.calledWith(null).should.equal true
  93. describe "when the lock times out", ->
  94. beforeEach (done) ->
  95. time = Date.now()
  96. @LockManager.MAX_LOCK_WAIT_TIME = 5
  97. @LockManager.tryLock = sinon.stub().callsArgWith(1, null, false)
  98. @LockManager.getLock @key, (args...) =>
  99. @callback(args...)
  100. done()
  101. it "should return the callback with an error", ->
  102. @callback.calledWith(new Error("timeout")).should.equal true
  103. describe "runWithLock", ->
  104. describe "with successful run", ->
  105. beforeEach ->
  106. @runner = (releaseLock = (error) ->) ->
  107. releaseLock()
  108. sinon.spy @, "runner"
  109. @LockManager.getLock = sinon.stub().callsArg(1)
  110. @LockManager.releaseLock = sinon.stub().callsArg(1)
  111. @LockManager.runWithLock @key, @runner, @callback
  112. it "should get the lock", ->
  113. @LockManager.getLock
  114. .calledWith(@key)
  115. .should.equal true
  116. it "should run the passed function", ->
  117. @runner.called.should.equal true
  118. it "should release the lock", ->
  119. @LockManager.releaseLock
  120. .calledWith(@key)
  121. .should.equal true
  122. it "should call the callback", ->
  123. @callback.called.should.equal true
  124. describe "when the runner function returns an error", ->
  125. beforeEach ->
  126. @error = new Error("oops")
  127. @runner = (releaseLock = (error) ->) =>
  128. releaseLock(@error)
  129. sinon.spy @, "runner"
  130. @LockManager.getLock = sinon.stub().callsArg(1)
  131. @LockManager.releaseLock = sinon.stub().callsArg(1)
  132. @LockManager.runWithLock @key, @runner, @callback
  133. it "should release the lock", ->
  134. @LockManager.releaseLock
  135. .calledWith(@key)
  136. .should.equal true
  137. it "should call the callback with the error", ->
  138. @callback.calledWith(@error).should.equal true