LockManagerTests.coffee 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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.randomLock = sinon.stub().returns "locked-random-value"
  41. @LockManager.tryLock @key, @callback
  42. it "should check the lock in redis", ->
  43. @rclient.set
  44. .calledWith(@key, "locked-random-value", "EX", @LockManager.LOCK_TTL, "NX")
  45. .should.equal true
  46. it "should return the callback with false", ->
  47. @callback.calledWith(null, false).should.equal true
  48. describe "when the lock is free", ->
  49. beforeEach ->
  50. @rclient.set = sinon.stub().callsArgWith(5, null, "OK")
  51. @LockManager.tryLock @key, @callback
  52. it "should return the callback with true", ->
  53. @callback.calledWith(null, true).should.equal true
  54. describe "deleteLock", ->
  55. beforeEach ->
  56. beforeEach ->
  57. @rclient.del = sinon.stub().callsArg(1)
  58. @LockManager.deleteLock @key, @callback
  59. it "should delete the lock in redis", ->
  60. @rclient.del
  61. .calledWith(key)
  62. .should.equal true
  63. it "should call the callback", ->
  64. @callback.called.should.equal true
  65. describe "getLock", ->
  66. describe "when the lock is not taken", ->
  67. beforeEach (done) ->
  68. @LockManager.tryLock = sinon.stub().callsArgWith(1, null, true)
  69. @LockManager.getLock @key, (args...) =>
  70. @callback(args...)
  71. done()
  72. it "should try to get the lock", ->
  73. @LockManager.tryLock
  74. .calledWith(@key)
  75. .should.equal true
  76. it "should only need to try once", ->
  77. @LockManager.tryLock.callCount.should.equal 1
  78. it "should return the callback", ->
  79. @callback.calledWith(null).should.equal true
  80. describe "when the lock is initially set", ->
  81. beforeEach (done) ->
  82. startTime = Date.now()
  83. @LockManager.LOCK_TEST_INTERVAL = 5
  84. @LockManager.tryLock = (doc_id, callback = (error, isFree) ->) ->
  85. if Date.now() - startTime < 100
  86. callback null, false
  87. else
  88. callback null, true
  89. sinon.spy @LockManager, "tryLock"
  90. @LockManager.getLock @key, (args...) =>
  91. @callback(args...)
  92. done()
  93. it "should call tryLock multiple times until free", ->
  94. (@LockManager.tryLock.callCount > 1).should.equal true
  95. it "should return the callback", ->
  96. @callback.calledWith(null).should.equal true
  97. describe "when the lock times out", ->
  98. beforeEach (done) ->
  99. time = Date.now()
  100. @LockManager.MAX_LOCK_WAIT_TIME = 5
  101. @LockManager.tryLock = sinon.stub().callsArgWith(1, null, false)
  102. @LockManager.getLock @key, (args...) =>
  103. @callback(args...)
  104. done()
  105. it "should return the callback with an error", ->
  106. @callback.calledWith(sinon.match.instanceOf(Error)).should.equal true
  107. describe "runWithLock", ->
  108. describe "with successful run", ->
  109. beforeEach ->
  110. @runner = (releaseLock = (error) ->) ->
  111. releaseLock()
  112. sinon.spy @, "runner"
  113. @LockManager.getLock = sinon.stub().callsArg(1)
  114. @LockManager.releaseLock = sinon.stub().callsArg(2)
  115. @LockManager.runWithLock @key, @runner, @callback
  116. it "should get the lock", ->
  117. @LockManager.getLock
  118. .calledWith(@key)
  119. .should.equal true
  120. it "should run the passed function", ->
  121. @runner.called.should.equal true
  122. it "should release the lock", ->
  123. @LockManager.releaseLock
  124. .calledWith(@key)
  125. .should.equal true
  126. it "should call the callback", ->
  127. @callback.called.should.equal true
  128. describe "when the runner function returns an error", ->
  129. beforeEach ->
  130. @error = new Error("oops")
  131. @runner = (releaseLock = (error) ->) =>
  132. releaseLock(@error)
  133. sinon.spy @, "runner"
  134. @LockManager.getLock = sinon.stub().callsArg(1)
  135. @LockManager.releaseLock = sinon.stub().callsArg(2)
  136. @LockManager.runWithLock @key, @runner, @callback
  137. it "should release the lock", ->
  138. @LockManager.releaseLock
  139. .calledWith(@key)
  140. .should.equal true
  141. it "should call the callback with the error", ->
  142. @callback.calledWith(@error).should.equal true