nvim.lua 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. local pretty = require 'pl.pretty'
  2. local global_helpers = require('test.helpers')
  3. -- Colors are disabled by default. #15610
  4. local colors = setmetatable({}, {__index = function() return function(s) return s == nil and '' or tostring(s) end end})
  5. if os.getenv "TEST_COLORS" then
  6. colors = require 'term.colors'
  7. end
  8. return function(options)
  9. local busted = require 'busted'
  10. local handler = require 'busted.outputHandlers.base'()
  11. local c = {
  12. succ = function(s) return colors.bright(colors.green(s)) end,
  13. skip = function(s) return colors.bright(colors.yellow(s)) end,
  14. fail = function(s) return colors.bright(colors.magenta(s)) end,
  15. errr = function(s) return colors.bright(colors.red(s)) end,
  16. test = tostring,
  17. file = colors.cyan,
  18. time = colors.dim,
  19. note = colors.yellow,
  20. sect = function(s) return colors.green(colors.dim(s)) end,
  21. nmbr = colors.bright,
  22. }
  23. local repeatSuiteString = '\nRepeating all tests (run %d of %d) . . .\n\n'
  24. local randomizeString = c.note('Note: Randomizing test order with a seed of %d.\n')
  25. local globalSetup = c.sect('--------') .. ' Global test environment setup.\n'
  26. local fileStartString = c.sect('--------') .. ' Running tests from ' .. c.file('%s') .. '\n'
  27. local runString = c.sect('RUN ') .. ' ' .. c.test('%s') .. ': '
  28. local successString = c.succ('OK') .. '\n'
  29. local skippedString = c.skip('SKIP') .. '\n'
  30. local failureString = c.fail('FAIL') .. '\n'
  31. local errorString = c.errr('ERR') .. '\n'
  32. local fileEndString = c.sect('--------') .. ' '.. c.nmbr('%d') .. ' %s from ' .. c.file('%s') .. ' ' .. c.time('(%.2f ms total)') .. '\n\n'
  33. local globalTeardown = c.sect('--------') .. ' Global test environment teardown.\n'
  34. local suiteEndString = c.sect('========') .. ' ' .. c.nmbr('%d') .. ' %s from ' .. c.nmbr('%d') .. ' test %s ran. ' .. c.time('(%.2f ms total)') .. '\n'
  35. local successStatus = c.succ('PASSED ') .. ' ' .. c.nmbr('%d') .. ' %s.\n'
  36. local timeString = c.time('%.2f ms')
  37. local summaryStrings = {
  38. skipped = {
  39. header = c.skip('SKIPPED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
  40. test = c.skip('SKIPPED ') .. ' %s\n',
  41. footer = ' ' .. c.nmbr('%d') .. ' SKIPPED %s\n',
  42. },
  43. failure = {
  44. header = c.fail('FAILED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
  45. test = c.fail('FAILED ') .. ' %s\n',
  46. footer = ' ' .. c.nmbr('%d') .. ' FAILED %s\n',
  47. },
  48. error = {
  49. header = c.errr('ERROR ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
  50. test = c.errr('ERROR ') .. ' %s\n',
  51. footer = ' ' .. c.nmbr('%d') .. ' %s\n',
  52. },
  53. }
  54. local fileCount = 0
  55. local fileTestCount = 0
  56. local testCount = 0
  57. local successCount = 0
  58. local skippedCount = 0
  59. local failureCount = 0
  60. local errorCount = 0
  61. local pendingDescription = function(pending)
  62. local string = ''
  63. if type(pending.message) == 'string' then
  64. string = string .. pending.message .. '\n'
  65. elseif pending.message ~= nil then
  66. string = string .. pretty.write(pending.message) .. '\n'
  67. end
  68. return string
  69. end
  70. local failureDescription = function(failure)
  71. local string = failure.randomseed and ('Random seed: ' .. failure.randomseed .. '\n') or ''
  72. if type(failure.message) == 'string' then
  73. string = string .. failure.message
  74. elseif failure.message == nil then
  75. string = string .. 'Nil error'
  76. else
  77. string = string .. pretty.write(failure.message)
  78. end
  79. string = string .. '\n'
  80. if options.verbose and failure.trace and failure.trace.traceback then
  81. string = string .. failure.trace.traceback .. '\n'
  82. end
  83. return string
  84. end
  85. local getFileLine = function(element)
  86. local fileline = ''
  87. if element.trace or element.trace.short_src then
  88. fileline = colors.cyan(element.trace.short_src) .. ' @ ' ..
  89. colors.cyan(element.trace.currentline) .. ': '
  90. end
  91. return fileline
  92. end
  93. local getTestList = function(status, count, list, getDescription)
  94. local string = ''
  95. local header = summaryStrings[status].header
  96. if count > 0 and header then
  97. local tests = (count == 1 and 'test' or 'tests')
  98. local errors = (count == 1 and 'error' or 'errors')
  99. string = header:format(count, status == 'error' and errors or tests)
  100. local testString = summaryStrings[status].test
  101. if testString then
  102. for _, t in ipairs(list) do
  103. local fullname = getFileLine(t.element) .. colors.bright(t.name)
  104. string = string .. testString:format(fullname)
  105. string = string .. getDescription(t)
  106. end
  107. end
  108. end
  109. return string
  110. end
  111. local getSummary = function(status, count)
  112. local string = ''
  113. local footer = summaryStrings[status].footer
  114. if count > 0 and footer then
  115. local tests = (count == 1 and 'TEST' or 'TESTS')
  116. local errors = (count == 1 and 'ERROR' or 'ERRORS')
  117. string = footer:format(count, status == 'error' and errors or tests)
  118. end
  119. return string
  120. end
  121. local getSummaryString = function()
  122. local tests = (successCount == 1 and 'test' or 'tests')
  123. local string = successStatus:format(successCount, tests)
  124. string = string .. getTestList('skipped', skippedCount, handler.pendings, pendingDescription)
  125. string = string .. getTestList('failure', failureCount, handler.failures, failureDescription)
  126. string = string .. getTestList('error', errorCount, handler.errors, failureDescription)
  127. string = string .. ((skippedCount + failureCount + errorCount) > 0 and '\n' or '')
  128. string = string .. getSummary('skipped', skippedCount)
  129. string = string .. getSummary('failure', failureCount)
  130. string = string .. getSummary('error', errorCount)
  131. return string
  132. end
  133. handler.suiteReset = function()
  134. fileCount = 0
  135. fileTestCount = 0
  136. testCount = 0
  137. successCount = 0
  138. skippedCount = 0
  139. failureCount = 0
  140. errorCount = 0
  141. return nil, true
  142. end
  143. handler.suiteStart = function(_suite, count, total, randomseed)
  144. if total > 1 then
  145. io.write(repeatSuiteString:format(count, total))
  146. end
  147. if randomseed then
  148. io.write(randomizeString:format(randomseed))
  149. end
  150. io.write(globalSetup)
  151. io.flush()
  152. return nil, true
  153. end
  154. local function getElapsedTime(tbl)
  155. if tbl.duration then
  156. return tbl.duration * 1000
  157. else
  158. return tonumber('nan')
  159. end
  160. end
  161. handler.suiteEnd = function(suite, _count, _total)
  162. local elapsedTime_ms = getElapsedTime(suite)
  163. local tests = (testCount == 1 and 'test' or 'tests')
  164. local files = (fileCount == 1 and 'file' or 'files')
  165. io.write(globalTeardown)
  166. io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms))
  167. io.write(getSummaryString())
  168. if failureCount > 0 or errorCount > 0 then
  169. io.write(global_helpers.read_nvim_log(nil, true))
  170. end
  171. io.flush()
  172. return nil, true
  173. end
  174. handler.fileStart = function(file)
  175. fileTestCount = 0
  176. io.write(fileStartString:format(file.name))
  177. io.flush()
  178. return nil, true
  179. end
  180. handler.fileEnd = function(file)
  181. local elapsedTime_ms = getElapsedTime(file)
  182. local tests = (fileTestCount == 1 and 'test' or 'tests')
  183. fileCount = fileCount + 1
  184. io.write(fileEndString:format(fileTestCount, tests, file.name, elapsedTime_ms))
  185. io.flush()
  186. return nil, true
  187. end
  188. handler.testStart = function(element, _parent)
  189. local testid = _G._nvim_test_id or ''
  190. local desc = ('%s %s'):format(testid, handler.getFullName(element))
  191. io.write(runString:format(desc))
  192. io.flush()
  193. return nil, true
  194. end
  195. local function write_status(element, string)
  196. io.write(timeString:format(getElapsedTime(element)) .. ' ' .. string)
  197. io.flush()
  198. end
  199. handler.testEnd = function(element, _parent, status, _debug)
  200. local string
  201. fileTestCount = fileTestCount + 1
  202. testCount = testCount + 1
  203. if status == 'success' then
  204. successCount = successCount + 1
  205. string = successString
  206. elseif status == 'pending' then
  207. skippedCount = skippedCount + 1
  208. string = skippedString
  209. elseif status == 'failure' then
  210. failureCount = failureCount + 1
  211. string = failureString .. failureDescription(handler.failures[#handler.failures])
  212. elseif status == 'error' then
  213. errorCount = errorCount + 1
  214. string = errorString .. failureDescription(handler.errors[#handler.errors])
  215. else
  216. string = "unexpected test status! ("..status..")"
  217. end
  218. write_status(element, string)
  219. return nil, true
  220. end
  221. handler.error = function(element, _parent, _message, _debug)
  222. if element.descriptor ~= 'it' then
  223. write_status(element, failureDescription(handler.errors[#handler.errors]))
  224. errorCount = errorCount + 1
  225. end
  226. return nil, true
  227. end
  228. busted.subscribe({ 'suite', 'reset' }, handler.suiteReset)
  229. busted.subscribe({ 'suite', 'start' }, handler.suiteStart)
  230. busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
  231. busted.subscribe({ 'file', 'start' }, handler.fileStart)
  232. busted.subscribe({ 'file', 'end' }, handler.fileEnd)
  233. busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending })
  234. busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
  235. busted.subscribe({ 'failure' }, handler.error)
  236. busted.subscribe({ 'error' }, handler.error)
  237. return handler
  238. end