lust.lua 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. -- lust v0.1.0 - Lua test framework
  2. -- https://github.com/bjornbytes/lust
  3. -- MIT LICENSE
  4. local lust = {}
  5. lust.level = 0
  6. lust.passes = 0
  7. lust.errors = 0
  8. lust.befores = {}
  9. lust.afters = {}
  10. local red = string.char(27) .. '[31m'
  11. local green = string.char(27) .. '[32m'
  12. local normal = string.char(27) .. '[0m'
  13. local function indent(level) return string.rep('\t', level or lust.level) end
  14. function lust.describe(name, fn)
  15. print(indent() .. name)
  16. lust.level = lust.level + 1
  17. fn()
  18. lust.befores[lust.level] = {}
  19. lust.afters[lust.level] = {}
  20. lust.level = lust.level - 1
  21. end
  22. function lust.it(name, fn)
  23. for level = 1, lust.level do
  24. if lust.befores[level] then
  25. for i = 1, #lust.befores[level] do
  26. lust.befores[level][i](name)
  27. end
  28. end
  29. end
  30. local success, err = pcall(fn)
  31. if success then lust.passes = lust.passes + 1
  32. else lust.errors = lust.errors + 1 end
  33. local color = success and green or red
  34. local label = success and 'PASS' or 'FAIL'
  35. print(indent() .. color .. label .. normal .. ' ' .. name)
  36. if err then
  37. print(indent(lust.level + 1) .. red .. err .. normal)
  38. end
  39. for level = 1, lust.level do
  40. if lust.afters[level] then
  41. for i = 1, #lust.afters[level] do
  42. lust.afters[level][i](name)
  43. end
  44. end
  45. end
  46. end
  47. function lust.before(fn)
  48. lust.befores[lust.level] = lust.befores[lust.level] or {}
  49. table.insert(lust.befores[lust.level], fn)
  50. end
  51. function lust.after(fn)
  52. lust.afters[lust.level] = lust.afters[lust.level] or {}
  53. table.insert(lust.afters[lust.level], fn)
  54. end
  55. -- Assertions
  56. local function isa(v, x)
  57. if type(x) == 'string' then
  58. return type(v) == x,
  59. 'expected ' .. tostring(v) .. ' to be a ' .. x,
  60. 'expected ' .. tostring(v) .. ' to not be a ' .. x
  61. elseif type(x) == 'table' then
  62. if type(v) ~= 'table' then
  63. return false,
  64. 'expected ' .. tostring(v) .. ' to be a ' .. tostring(x),
  65. 'expected ' .. tostring(v) .. ' to not be a ' .. tostring(x)
  66. end
  67. local seen = {}
  68. local meta = v
  69. while meta and not seen[meta] do
  70. if meta == x then return true end
  71. seen[meta] = true
  72. meta = getmetatable(meta) and getmetatable(meta).__index
  73. end
  74. return false,
  75. 'expected ' .. tostring(v) .. ' to be a ' .. tostring(x),
  76. 'expected ' .. tostring(v) .. ' to not be a ' .. tostring(x)
  77. end
  78. error('invalid type ' .. tostring(x))
  79. end
  80. local function has(t, x)
  81. for k, v in pairs(t) do
  82. if v == x then return true end
  83. end
  84. return false
  85. end
  86. local function strict_eq(t1, t2)
  87. if type(t1) ~= type(t2) then return false end
  88. if type(t1) ~= 'table' then return t1 == t2 end
  89. if #t1 ~= #t2 then return false end
  90. for k, _ in pairs(t1) do
  91. if not strict_eq(t1[k], t2[k]) then return false end
  92. end
  93. for k, _ in pairs(t2) do
  94. if not strict_eq(t2[k], t1[k]) then return false end
  95. end
  96. return true
  97. end
  98. local paths = {
  99. [''] = { 'to', 'to_not' },
  100. to = { 'have', 'equal', 'be', 'exist', 'fail' },
  101. to_not = { 'have', 'equal', 'be', 'exist', 'fail', chain = function(a) a.negate = not a.negate end },
  102. a = { test = isa },
  103. an = { test = isa },
  104. be = { 'a', 'an', 'truthy',
  105. test = function(v, x)
  106. return v == x,
  107. 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to be equal',
  108. 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to not be equal'
  109. end
  110. },
  111. exist = {
  112. test = function(v)
  113. return v ~= nil,
  114. 'expected ' .. tostring(v) .. ' to exist',
  115. 'expected ' .. tostring(v) .. ' to not exist'
  116. end
  117. },
  118. truthy = {
  119. test = function(v)
  120. return v,
  121. 'expected ' .. tostring(v) .. ' to be truthy',
  122. 'expected ' .. tostring(v) .. ' to not be truthy'
  123. end
  124. },
  125. equal = {
  126. test = function(v, x)
  127. return strict_eq(v, x),
  128. 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to be exactly equal',
  129. 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to not be exactly equal'
  130. end
  131. },
  132. have = {
  133. test = function(v, x)
  134. if type(v) ~= 'table' then
  135. error('expected ' .. tostring(v) .. ' to be a table')
  136. end
  137. return has(v, x),
  138. 'expected ' .. tostring(v) .. ' to contain ' .. tostring(x),
  139. 'expected ' .. tostring(v) .. ' to not contain ' .. tostring(x)
  140. end
  141. },
  142. fail = {
  143. test = function(v)
  144. return not pcall(v),
  145. 'expected ' .. tostring(v) .. ' to fail',
  146. 'expected ' .. tostring(v) .. ' to not fail'
  147. end
  148. }
  149. }
  150. function lust.expect(v)
  151. local assertion = {}
  152. assertion.val = v
  153. assertion.action = ''
  154. assertion.negate = false
  155. setmetatable(assertion, {
  156. __index = function(t, k)
  157. if has(paths[rawget(t, 'action')], k) then
  158. rawset(t, 'action', k)
  159. local chain = paths[rawget(t, 'action')].chain
  160. if chain then chain(t) end
  161. return t
  162. end
  163. return rawget(t, k)
  164. end,
  165. __call = function(t, ...)
  166. if paths[t.action].f then
  167. local res, err, nerr = paths[t.action].f(t.val, ...)
  168. if assertion.negate then
  169. res = not res
  170. err = nerr or err
  171. end
  172. if not res then
  173. error(err or 'unknown failure', 2)
  174. end
  175. end
  176. end
  177. })
  178. return assertion
  179. end
  180. function lust.spy(target, name, run)
  181. local spy = {}
  182. local subject
  183. local function capture(...)
  184. table.insert(spy, {...})
  185. return subject(...)
  186. end
  187. if type(target) == 'table' then
  188. subject = target[name]
  189. target[name] = capture
  190. else
  191. run = name
  192. subject = target or function() end
  193. end
  194. setmetatable(spy, {__call = function(_, ...) return capture(...) end})
  195. if run then run() end
  196. return spy
  197. end
  198. lust.test = lust.it
  199. lust.paths = paths
  200. return lust