mispec.lua 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. local moduleName = ... or 'mispec'
  2. local M = {}
  3. _G[moduleName] = M
  4. -- Helpers:
  5. function ok(expression, desc)
  6. if expression == nil then expression = false end
  7. desc = desc or 'expression is not ok'
  8. if not expression then
  9. error(desc .. '\n' .. debug.traceback())
  10. end
  11. end
  12. function ko(expression, desc)
  13. if expression == nil then expression = false end
  14. desc = desc or 'expression is not ko'
  15. if expression then
  16. error(desc .. '\n' .. debug.traceback())
  17. end
  18. end
  19. function eq(a, b)
  20. if type(a) ~= type(b) then
  21. error('type ' .. type(a) .. ' is not equal to ' .. type(b) .. '\n' .. debug.traceback())
  22. end
  23. if type(a) == 'function' then
  24. return string.dump(a) == string.dump(b)
  25. end
  26. if a == b then return true end
  27. if type(a) ~= 'table' then
  28. error(string.format("%q",tostring(a)) .. ' is not equal to ' .. string.format("%q",tostring(b)) .. '\n' .. debug.traceback())
  29. end
  30. for k,v in pairs(a) do
  31. if b[k] == nil or not eq(v, b[k]) then return false end
  32. end
  33. for k,v in pairs(b) do
  34. if a[k] == nil or not eq(v, a[k]) then return false end
  35. end
  36. return true
  37. end
  38. function failwith(message, func, ...)
  39. local status, err = pcall(func, ...)
  40. if status then
  41. local messagePart = ""
  42. if message then
  43. messagePart = " containing \"" .. message .. "\""
  44. end
  45. error("Error expected" .. messagePart .. '\n' .. debug.traceback())
  46. end
  47. if (message and not string.find(err, message)) then
  48. error("expected errormessage \"" .. err .. "\" to contain \"" .. message .. "\"" .. '\n' .. debug.traceback() )
  49. end
  50. return true
  51. end
  52. function fail(func, ...)
  53. return failwith(nil, func, ...)
  54. end
  55. local function eventuallyImpl(func, retries, delayMs)
  56. local prevEventually = _G.eventually
  57. _G.eventually = function() error("Cannot nest eventually/andThen.") end
  58. local status, err = pcall(func)
  59. _G.eventually = prevEventually
  60. if status then
  61. M.queuedEventuallyCount = M.queuedEventuallyCount - 1
  62. M.runNextPending()
  63. else
  64. if retries > 0 then
  65. local t = tmr.create()
  66. t:register(delayMs, tmr.ALARM_SINGLE, M.runNextPending)
  67. t:start()
  68. table.insert(M.pending, 1, function() eventuallyImpl(func, retries - 1, delayMs) end)
  69. else
  70. M.failed = M.failed + 1
  71. print("\n ! it failed:", err)
  72. -- remove all pending eventuallies as spec has failed at this point
  73. for i = 1, M.queuedEventuallyCount - 1 do
  74. table.remove(M.pending, 1)
  75. end
  76. M.queuedEventuallyCount = 0
  77. M.runNextPending()
  78. end
  79. end
  80. end
  81. function eventually(func, retries, delayMs)
  82. retries = retries or 10
  83. delayMs = delayMs or 300
  84. M.queuedEventuallyCount = M.queuedEventuallyCount + 1
  85. table.insert(M.pending, M.queuedEventuallyCount, function()
  86. eventuallyImpl(func, retries, delayMs)
  87. end)
  88. end
  89. function andThen(func)
  90. eventually(func, 0, 0)
  91. end
  92. function describe(name, itshoulds)
  93. M.name = name
  94. M.itshoulds = itshoulds
  95. end
  96. -- Module:
  97. M.runNextPending = function()
  98. local next = table.remove(M.pending, 1)
  99. if next then
  100. node.task.post(next)
  101. next = nil
  102. else
  103. M.succeeded = M.total - M.failed
  104. local elapsedSeconds = (tmr.now() - M.startTime) / 1000 / 1000
  105. print(string.format(
  106. '\n\nCompleted in %d seconds; %d failed out of %d.',
  107. elapsedSeconds, M.failed, M.total))
  108. M.pending = nil
  109. M.queuedEventuallyCount = nil
  110. end
  111. end
  112. M.run = function()
  113. M.pending = {}
  114. M.queuedEventuallyCount = 0
  115. M.startTime = tmr.now()
  116. M.total = 0
  117. M.failed = 0
  118. local it = {}
  119. it.should = function(_, desc, func)
  120. table.insert(M.pending, function()
  121. print('\n * ' .. desc)
  122. M.total = M.total + 1
  123. if M.pre then M.pre() end
  124. local status, err = pcall(func)
  125. if not status then
  126. print("\n ! it failed:", err)
  127. M.failed = M.failed + 1
  128. end
  129. if M.post then M.post() end
  130. M.runNextPending()
  131. end)
  132. end
  133. it.initialize = function(_, pre) M.pre = pre end;
  134. it.cleanup = function(_, post) M.post = post end;
  135. M.itshoulds(it)
  136. print('' .. M.name .. ', it should:')
  137. M.runNextPending()
  138. M.itshoulds = nil
  139. M.name = nil
  140. end
  141. print ("loaded mispec")