LockManagerTests.coffee 5.0 KB

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