nvim.lua (10996B)
1 local pretty = require 'pl.pretty' 2 local t_global = require('test.testutil') 3 4 local colors = setmetatable({}, { 5 __index = function() 6 return function(s) 7 return s == nil and '' or tostring(s) 8 end 9 end, 10 }) 11 12 local enable_colors = true 13 if os.getenv 'TEST_COLORS' then 14 local test_colors = os.getenv('TEST_COLORS'):lower() 15 local disable_colors = test_colors == 'false' 16 or test_colors == '0' 17 or test_colors == 'no' 18 or test_colors == 'off' 19 enable_colors = not disable_colors 20 end 21 if enable_colors then 22 colors = require 'term.colors' 23 end 24 25 return function(options) 26 local busted = require 'busted' 27 local handler = require 'busted.outputHandlers.base'() 28 local args = options.arguments 29 args = vim.json.decode(#args > 0 and table.concat(args, ',') or '{}') 30 31 local c = { 32 succ = function(s) 33 return colors.bright(colors.green(s)) 34 end, 35 skip = function(s) 36 return colors.bright(colors.yellow(s)) 37 end, 38 fail = function(s) 39 return colors.bright(colors.magenta(s)) 40 end, 41 errr = function(s) 42 return colors.bright(colors.red(s)) 43 end, 44 test = tostring, 45 file = colors.cyan, 46 time = colors.dim, 47 note = colors.yellow, 48 sect = function(s) 49 return colors.green(colors.dim(s)) 50 end, 51 nmbr = colors.bright, 52 } 53 54 local repeatSuiteString = '\nRepeating all tests (run %d of %d) . . .\n\n' 55 local randomizeString = c.note('Note: Randomizing test order with a seed of %d.\n') 56 local globalSetup = c.sect('--------') .. ' Global test environment setup.\n' 57 local fileStartString = c.sect('--------') .. ' Running tests from ' .. c.file('%s') .. '\n' 58 local runString = c.sect('RUN ') .. ' ' .. c.test('%s') .. ': ' 59 local successString = c.succ('OK') .. '\n' 60 local skippedString = c.skip('SKIP') .. '\n' 61 local failureString = c.fail('FAIL') .. '\n' 62 local errorString = c.errr('ERR') .. '\n' 63 local fileEndString = c.sect('--------') 64 .. ' ' 65 .. c.nmbr('%d') 66 .. ' %s from ' 67 .. c.file('%s') 68 .. ' ' 69 .. c.time('(%.2f ms total)') 70 .. '\n\n' 71 local globalTeardown = c.sect('--------') .. ' Global test environment teardown.\n' 72 local suiteEndString = c.sect('========') 73 .. ' ' 74 .. c.nmbr('%d') 75 .. ' %s from ' 76 .. c.nmbr('%d') 77 .. ' test %s ran. ' 78 .. c.time('(%.2f ms total)') 79 .. '\n' 80 local successStatus = c.succ('PASSED ') .. ' ' .. c.nmbr('%d') .. ' %s.\n' 81 local timeString = c.time('%.2f ms') 82 83 local summaryStrings = { 84 skipped = { 85 header = c.skip('SKIPPED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', 86 test = c.skip('SKIPPED ') .. ' %s\n', 87 footer = ' ' .. c.nmbr('%d') .. ' SKIPPED %s\n', 88 }, 89 90 failure = { 91 header = c.fail('FAILED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', 92 test = c.fail('FAILED ') .. ' %s\n', 93 footer = ' ' .. c.nmbr('%d') .. ' FAILED %s\n', 94 }, 95 96 error = { 97 header = c.errr('ERROR ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', 98 test = c.errr('ERROR ') .. ' %s\n', 99 footer = ' ' .. c.nmbr('%d') .. ' %s\n', 100 }, 101 } 102 103 local fileCount = 0 104 local fileTestCount = 0 105 local testCount = 0 106 local successCount = 0 107 local skippedCount = 0 108 local failureCount = 0 109 local errorCount = 0 110 111 local naCheck = function(pending) 112 if vim.list_contains(vim.split(pending.name, '[ :]'), 'N/A') then 113 return true 114 end 115 if type(pending.message) ~= 'string' then 116 return false 117 end 118 if vim.list_contains(vim.split(pending.message, '[ :]'), 'N/A') then 119 return true 120 end 121 return false 122 end 123 124 local pendingDescription = function(pending) 125 local string = '' 126 127 if type(pending.message) == 'string' then 128 string = string .. pending.message .. '\n' 129 elseif pending.message ~= nil then 130 string = string .. pretty.write(pending.message) .. '\n' 131 end 132 133 return string 134 end 135 136 local failureDescription = function(failure) 137 local string = failure.randomseed and ('Random seed: ' .. failure.randomseed .. '\n') or '' 138 if type(failure.message) == 'string' then 139 string = string .. failure.message 140 elseif failure.message == nil then 141 string = string .. 'Nil error' 142 else 143 string = string .. pretty.write(failure.message) 144 end 145 146 string = string .. '\n' 147 148 if options.verbose and failure.trace and failure.trace.traceback then 149 string = string .. failure.trace.traceback .. '\n' 150 end 151 152 return string 153 end 154 155 local getFileLine = function(element) 156 local fileline = '' 157 if element.trace or element.trace.short_src then 158 local fname = vim.fs.normalize(element.trace.short_src) 159 fileline = colors.cyan(fname) .. ' @ ' .. colors.cyan(element.trace.currentline) .. ': ' 160 end 161 return fileline 162 end 163 164 local getTestList = function(status, count, list, getDescription) 165 local string = '' 166 local header = summaryStrings[status].header 167 if count > 0 and header then 168 local tests = (count == 1 and 'test' or 'tests') 169 local errors = (count == 1 and 'error' or 'errors') 170 string = header:format(count, status == 'error' and errors or tests) 171 172 local testString = summaryStrings[status].test 173 if testString then 174 local naCount = 0 175 for _, t in ipairs(list) do 176 if status == 'skipped' and naCheck(t) then 177 naCount = naCount + 1 178 else 179 local fullname = getFileLine(t.element) .. colors.bright(t.name) 180 string = string .. testString:format(fullname) 181 string = string .. getDescription(t) 182 end 183 end 184 if naCount > 0 then 185 string = string 186 .. colors.bright( 187 ('%d N/A %s not shown\n'):format(naCount, naCount == 1 and 'test' or 'tests') 188 ) 189 end 190 end 191 end 192 return string 193 end 194 195 local getSummary = function(status, count) 196 local string = '' 197 local footer = summaryStrings[status].footer 198 if count > 0 and footer then 199 local tests = (count == 1 and 'TEST' or 'TESTS') 200 local errors = (count == 1 and 'ERROR' or 'ERRORS') 201 string = footer:format(count, status == 'error' and errors or tests) 202 end 203 return string 204 end 205 206 local getSummaryString = function() 207 local tests = (successCount == 1 and 'test' or 'tests') 208 local string = successStatus:format(successCount, tests) 209 210 string = string .. getTestList('skipped', skippedCount, handler.pendings, pendingDescription) 211 string = string .. getTestList('failure', failureCount, handler.failures, failureDescription) 212 string = string .. getTestList('error', errorCount, handler.errors, failureDescription) 213 214 string = string .. ((skippedCount + failureCount + errorCount) > 0 and '\n' or '') 215 string = string .. getSummary('skipped', skippedCount) 216 string = string .. getSummary('failure', failureCount) 217 string = string .. getSummary('error', errorCount) 218 219 return string 220 end 221 222 handler.suiteReset = function() 223 fileCount = 0 224 fileTestCount = 0 225 testCount = 0 226 successCount = 0 227 skippedCount = 0 228 failureCount = 0 229 errorCount = 0 230 231 return nil, true 232 end 233 234 handler.suiteStart = function(_suite, count, total, randomseed) 235 if total > 1 then 236 io.write(repeatSuiteString:format(count, total)) 237 end 238 if randomseed then 239 io.write(randomizeString:format(randomseed)) 240 end 241 io.write(globalSetup) 242 io.flush() 243 244 return nil, true 245 end 246 247 local function getElapsedTime(tbl) 248 if tbl.duration then 249 return tbl.duration * 1000 250 else 251 return tonumber('nan') 252 end 253 end 254 255 handler.suiteEnd = function(suite, _count, _total) 256 local elapsedTime_ms = getElapsedTime(suite) 257 local tests = (testCount == 1 and 'test' or 'tests') 258 local files = (fileCount == 1 and 'file' or 'files') 259 if type(args.test_path) == 'string' then 260 files = files .. ' of ' .. args.test_path 261 end 262 local sf = type(args.summary_file) == 'string' 263 and args.summary_file ~= '-' 264 and io.open(args.summary_file, 'w') 265 or io.stdout 266 io.write(globalTeardown) 267 io.flush() 268 sf:write('\n') 269 sf:write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms)) 270 sf:write(getSummaryString()) 271 if failureCount > 0 or errorCount > 0 then 272 sf:write(t_global.read_nvim_log(nil, true)) 273 end 274 sf:flush() 275 if sf ~= io.stdout then 276 sf:close() 277 end 278 279 return nil, true 280 end 281 282 handler.fileStart = function(file) 283 fileTestCount = 0 284 io.write(fileStartString:format(vim.fs.normalize(file.name))) 285 io.flush() 286 return nil, true 287 end 288 289 handler.fileEnd = function(file) 290 local elapsedTime_ms = getElapsedTime(file) 291 local tests = (fileTestCount == 1 and 'test' or 'tests') 292 fileCount = fileCount + 1 293 io.write( 294 fileEndString:format(fileTestCount, tests, vim.fs.normalize(file.name), elapsedTime_ms) 295 ) 296 io.flush() 297 return nil, true 298 end 299 300 handler.testStart = function(element, _parent) 301 local testid = _G._nvim_test_id or '' 302 local desc = ('%s %s'):format(testid, handler.getFullName(element)) 303 io.write(runString:format(desc)) 304 io.flush() 305 306 return nil, true 307 end 308 309 local function write_status(element, string) 310 io.write(timeString:format(getElapsedTime(element)) .. ' ' .. string) 311 io.flush() 312 end 313 314 handler.testEnd = function(element, _parent, status, _debug) 315 local string 316 317 fileTestCount = fileTestCount + 1 318 testCount = testCount + 1 319 if status == 'success' then 320 successCount = successCount + 1 321 string = successString 322 elseif status == 'pending' then 323 skippedCount = skippedCount + 1 324 string = skippedString 325 elseif status == 'failure' then 326 failureCount = failureCount + 1 327 string = failureString .. failureDescription(handler.failures[#handler.failures]) 328 elseif status == 'error' then 329 errorCount = errorCount + 1 330 string = errorString .. failureDescription(handler.errors[#handler.errors]) 331 else 332 string = 'unexpected test status! (' .. status .. ')' 333 end 334 write_status(element, string) 335 336 return nil, true 337 end 338 339 handler.error = function(element, _parent, _message, _debug) 340 if element.descriptor ~= 'it' then 341 write_status(element, failureDescription(handler.errors[#handler.errors])) 342 errorCount = errorCount + 1 343 end 344 345 return nil, true 346 end 347 348 busted.subscribe({ 'suite', 'reset' }, handler.suiteReset) 349 busted.subscribe({ 'suite', 'start' }, handler.suiteStart) 350 busted.subscribe({ 'suite', 'end' }, handler.suiteEnd) 351 busted.subscribe({ 'file', 'start' }, handler.fileStart) 352 busted.subscribe({ 'file', 'end' }, handler.fileEnd) 353 busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending }) 354 busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending }) 355 busted.subscribe({ 'failure' }, handler.error) 356 busted.subscribe({ 'error' }, handler.error) 357 358 return handler 359 end