neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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