diff --git a/lua/plenary/busted.lua b/lua/plenary/busted.lua index 57b36e6b..11e75556 100644 --- a/lua/plenary/busted.lua +++ b/lua/plenary/busted.lua @@ -1,19 +1,25 @@ +local Path = require('plenary.path') + local dirname = function(p) return vim.fn.fnamemodify(p, ":h") end local function get_trace(element, level, msg) + local function trimTrace(info) - local index = info.traceback:find('\n%s*%[C]') - info.traceback = info.traceback:sub(1, index) + local start_index = info.traceback:find('/') + local end_index = info.traceback:find(': in') + info.traceback = info.traceback:sub(start_index, end_index) + return info end + level = level or 3 - local thisdir = dirname(debug.getinfo(1, 'Sl').source, ":h") + local thisdir = dirname(debug.getinfo(1, 'Sl').source) local info = debug.getinfo(level, 'Sl') while info.what == 'C' or info.short_src:match('luassert[/\\].*%.lua$') or - (info.source:sub(1,1) == '@' and thisdir == dirname(info.source)) do + (info.source:sub(1,1) == '@' and thisdir == dirname(info.source)) do level = level + 1 info = debug.getinfo(level, 'Sl') end @@ -22,12 +28,44 @@ local function get_trace(element, level, msg) info.message = msg -- local file = busted.getFile(element) - local file = false - return file and file.getTrace(file.name, info) or trimTrace(info) + -- local file = false + -- local file = false + -- return file and file.getTrace(file.name, info) or trimTrace(info) + return trimTrace(info) end +local function get_file_and_line_number() + + local function trimTrace(trace) + local start_index = trace:find('/') + local end_index = trace:find(': in') + trace = trace:sub(start_index, end_index) + + local split_str = vim.split(trace, ':') + local spec = {} + spec.file = split_str[1] + spec.linenumber = split_str[2] + + return spec + end + + local level = 3 + + local thisdir = dirname(debug.getinfo(1, 'Sl').source) + local info = debug.getinfo(level, 'Sl') + while info.what == 'C' or info.short_src:match('luassert[/\\].*%.lua$') or + (info.source:sub(1,1) == '@' and thisdir == dirname(info.source)) do + level = level + 1 + info = debug.getinfo(level, 'Sl') + end + + local trace = debug.traceback('', level) + return trimTrace(trace) +end +--[[ is_headless is always true +-- running in nvim or in terminal --]] local is_headless = require('plenary.nvim_meta').is_headless local print = function(...) @@ -70,10 +108,9 @@ local call_inner = function(desc, func) local desc_stack = add_description(desc) add_new_each() local ok, msg = xpcall(func, function(msg) - -- debug.traceback - -- return vim.inspect(get_trace(nil, 3, msg)) local trace = get_trace(nil, 3, msg) - return trace.message .. "\n" .. trace.traceback + -- return trace.message .. "\n" .. trace.traceback + return trace.message end) clear_last_each() pop_description() @@ -81,7 +118,9 @@ local call_inner = function(desc, func) return ok, msg, desc_stack end -local color_table = { +local ansi_color_table = { + cyan = 36, + magenta = 35, yellow = 33, green = 32, red = 31, @@ -89,42 +128,49 @@ local color_table = { local color_string = function(color, str) if not is_headless then + -- This is never being called return str end return string.format("%s[%sm%s%s[%sm", - string.char(27), - color_table[color] or 0, - str, - string.char(27), - 0 + string.char(27), + ansi_color_table[color] or 0, + str, + string.char(27), + 0 ) end +local bold_string = function(str) + local ansi_bold = "\027[1m" + local ansi_clear = "\027[0m" + + return ansi_bold .. str .. ansi_clear +end + +-- local SUCCESS = color_string("green", "Success") +local FAIL = color_string("red", "Failure") local SUCCESS = color_string("green", "Success") -local FAIL = color_string("red", "Fail") local PENDING = color_string("yellow", "Pending") -local HEADER = string.rep("=", 40) +local HORIZONTALRULER = string.rep("─", 80) + +mod.format_results = function(result) -mod.format_results = function(res) - local num_pass = #res.pass - local num_fail = #res.fail - local num_errs = #res.errs + local num_pass = color_string("green", #result.pass) + local num_fail = color_string("red", #result.fail) + local num_errs = color_string("magenta", #result.errs) - print("") - print(color_string("green", "Success: "), num_pass) - print(color_string("red", "Failed : "), num_fail) - print(color_string("red", "Errors : "), num_errs) - print(HEADER) + print(string.format(" %s successes / %s failures / %s errors", num_pass, num_fail, num_errs)) end mod.describe = function(desc, func) - results.pass = results.pass or {} - results.fail = results.fail or {} - results.errs = results.errs or {} - results.fatal = results.fatal or {} + results.pass = {} + results.fail = {} + results.errs = {} + print("\n" .. HORIZONTALRULER .."\n ") + -- print("Testing: ", debug.getinfo(2, 'Sl').source) describe = mod.inner_describe local ok, msg = call_inner(desc, func) describe = mod.describe @@ -185,17 +231,27 @@ mod.it = function(desc, func) -- TODO: We should figure out how to determine whether -- and assert failed or whether it was an error... - local to_insert, printed if not ok then to_insert = results.fail test_result.msg = msg - print(FAIL, "||", table.concat(test_result.descriptions, " ")) - print(indent(msg, 12)) + -- print(FAIL, " → " .. color_string("cyan", "spec/foo/bar_spec.lua @ 7") .. "\n") + + print("{SPEC: FAIL}") + local spec = get_file_and_line_number() + + print(FAIL, " → " .. color_string("cyan", spec.file) .. " @ " .. color_string("cyan", spec.linenumber) .. "\n") + + print(bold_string(table.concat(test_result.descriptions))) + print(indent("\n" .. msg, 7)) + + print("{ENDOFSPEC}") + else - to_insert = results.pass - print(SUCCESS, "||", table.concat(test_result.descriptions, " ")) + print("{SPEC: SUCCESS}") + print(SUCCESS, " → ", table.concat(test_result.descriptions, " ")) + print("{ENDOFSPEC}") end table.insert(to_insert, test_result) @@ -219,16 +275,17 @@ clear = mod.clear assert = require("luassert") mod.run = function(file) - print("\n" .. HEADER) - print("Testing: ", file) + -- print("Testing: ", file) local ok, msg = pcall(dofile, file) if not ok then - print(HEADER) + print(HORIZONTALRULER) print("FAILED TO LOAD FILE") print(color_string("red", msg)) - print(HEADER) + print(HORIZONTALRULER) + + os.exit(2) if is_headless then os.exit(2) else diff --git a/lua/plenary/busted_dots.lua b/lua/plenary/busted_dots.lua new file mode 100644 index 00000000..b4fba71f --- /dev/null +++ b/lua/plenary/busted_dots.lua @@ -0,0 +1,64 @@ +local ansi_color_table = { + cyan = 36, + magenta = 35, + yellow = 33, + green = 32, + red = 31, +} + +local color_string = function(color, str) + return string.format("%s[%sm%s%s[%sm", + string.char(27), + ansi_color_table[color] or 0, + str, + string.char(27), + 0 + ) +end + +local successDot = color_string('green', '●') +local failureDot = color_string('red', '◼') +local errorDot = color_string('magenta', '✱') +local pendingDot = color_string('yellow', '◌') + +local function generate_dots(results, status_str, dot) + local dot_count = 0 + for _, spec in pairs(results) do + if spec.status == status_str then + io.stdout:write(dot) + dot_count = dot_count + 1 + end + end + return dot_count +end + +local function generate_score(n_succ, n_fail, n_err, n_pend) + + local success = n_succ == 1 and ' success' or ' successes' + local fail = n_fail == 1 and ' failure' or ' failures' + local error = n_err == 1 and ' error' or ' errors' + + local score = + color_string('green', n_succ) .. success .. ' / ' .. + color_string('red', n_fail) .. fail .. ' / ' .. + color_string('magenta', n_err) .. error .. ' / ' .. + color_string('yellow', n_pend) .. ' pending' + + io.stdout:write(score) +end + +local Score = {} + +function Score.draw(results) + + local n_pend = generate_dots(results, 'pending', pendingDot) + local n_err = generate_dots(results, 'error', errorDot) + local n_fail = generate_dots(results, 'failed', failureDot) + local n_succ = generate_dots(results, 'success', successDot) + + io.stdout:write('\n') + generate_score(n_succ, n_fail, n_err, n_pend) + io.stdout:write('\n\n') +end + +return Score diff --git a/lua/plenary/busted_reader.lua b/lua/plenary/busted_reader.lua new file mode 100644 index 00000000..09b9f78a --- /dev/null +++ b/lua/plenary/busted_reader.lua @@ -0,0 +1,49 @@ +BustedOutputReader = {} + + local spec_results = {} + local current_spec = {} + + local function set_spec_status(line, find_str, status_str) + if line:find(find_str) then + current_spec.status = status_str + return true + end + end + + local is_spec_result + local cat_line + + local function reset() + current_spec, cat_line, is_spec_result = {}, nil, nil + end + + BustedOutputReader.output_to_table = function(...) + + for _, line in pairs({...}) do + + if is_spec_result then + + if line:find('{ENDOFSPEC}') then + current_spec.content = cat_line + table.insert(spec_results, current_spec) + + reset() + else + if not cat_line then cat_line = "" end + cat_line = cat_line .. line .. '\n' + end + end + + if not is_spec_result then + is_spec_result = set_spec_status(line, '{SPEC: FAIL}', 'failed') or + set_spec_status(line, '{SPEC: ERROR}', 'error') or + set_spec_status(line, '{SPEC: SUCCESS}', 'success') or + set_spec_status(line, '{SPEC: PENDING}', 'pending') + end + end + + return spec_results +end + +return BustedOutputReader + diff --git a/lua/plenary/job.lua b/lua/plenary/job.lua index fcd39820..34131f23 100644 --- a/lua/plenary/job.lua +++ b/lua/plenary/job.lua @@ -67,6 +67,7 @@ end ---@field on_exit function : (self, code: number, signal: number) ---@field maximum_results number : stop processing results after this number ---@field writer Job|table|string : Job that writes to stdin of this job. + function Job:new(o) if not o then error(debug.traceback("Options are required for Job:new")) diff --git a/lua/plenary/test_harness.lua b/lua/plenary/test_harness.lua index 1c4bf461..df985bc1 100644 --- a/lua/plenary/test_harness.lua +++ b/lua/plenary/test_harness.lua @@ -9,13 +9,23 @@ local headless = require("plenary.nvim_meta").is_headless local harness = {} -local print_output = vim.schedule_wrap(function(_, ...) - for _, v in ipairs({...}) do - io.stdout:write(tostring(v)) - io.stdout:write("\n") - end +-- local tty_output = vim.schedule_wrap(function(_, ...) +-- for _, v in ipairs({...}) do +-- io.stdout:write(tostring(v)) +-- io.stdout:write("\n") +-- end + +-- vim.cmd [[mode]] +-- end) + +local test_res = {} + +local tty_output = vim.schedule_wrap(function(_, ...) + + local read = require('plenary.busted_reader') + test_res = read.output_to_table(...) - vim.cmd [[mode]] + vim.cmd [[mode]] end) local nvim_output = vim.schedule_wrap(function(bufnr, ...) @@ -24,6 +34,10 @@ local nvim_output = vim.schedule_wrap(function(bufnr, ...) end for _, v in ipairs({...}) do + + local skip = v:find('{SPEC:') or v:find('{ENDOFSPEC}') + if skip then return end + vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {v}) end end) @@ -38,7 +52,7 @@ function harness.test_directory_command(command) end function harness.test_directory(directory, opts) - print("Starting...") + print("Start Busting...\n") opts = vim.tbl_deep_extend('force', {winopts = {winblend = 3}}, opts or {}) local res = {} @@ -58,11 +72,13 @@ function harness.test_directory(directory, opts) vim.cmd('mode') end - local outputter = headless and print_output or nvim_output + local outputter = headless and tty_output or nvim_output local paths = harness._find_files_to_run(directory) for _, p in ipairs(paths) do - outputter(res.bufnr, "Scheduling: " .. p.filename) + local headless_ctx = headless and 'Scheduling: ' or 'Current: ' + local rel_path = Path.make_relative(p) + outputter(res.bufnr, headless_ctx .. rel_path) end local path_len = #paths @@ -100,10 +116,10 @@ function harness.test_directory(directory, opts) end, on_exit = vim.schedule_wrap(function(j_self, _, _) - if path_len ~= 1 then - outputter(res.bufnr, unpack(j_self:stderr_result())) - outputter(res.bufnr, unpack(j_self:result())) - end + if path_len ~= 1 then + outputter(res.bufnr, unpack(j_self:stderr_result())) + outputter(res.bufnr, unpack(j_self:result())) + end vim.cmd('mode') end) @@ -130,6 +146,15 @@ function harness.test_directory(directory, opts) vim.wait(100) log.debug("Done...") + local dots = require('plenary.busted_dots') + dots.draw(test_res) + + for _, spec in pairs(test_res) do + -- io.stdout:write(tostring(res.status), " \n") + io.stdout:write(spec.content, " \n") + -- print("res table" .. tostring(res)) + end + if headless then if f.any(function(_, v) return v.code ~= 0 end, jobs) then os.exit(1) diff --git a/plugin/plenary.vim b/plugin/plenary.vim index a6ae381e..9177fde6 100644 --- a/plugin/plenary.vim +++ b/plugin/plenary.vim @@ -1,4 +1,3 @@ - " Set up neorocks if it is installed. lua pcall(function() require('plenary.neorocks').setup_paths() end) @@ -9,4 +8,13 @@ command! -nargs=1 -complete=file PlenaryBustedFile command! -nargs=+ -complete=file PlenaryBustedDirectory \ lua require('plenary.test_harness').test_directory_command(vim.fn.expand("")) +function! s:write_and_run_testfile() abort + if &filetype == 'lua' + :write + :lua require('plenary.test_harness').test_directory(vim.fn.expand("%:p")) + endif + return +endfunction + +nnoremap PlenaryWriteAndRunTestFile :call write_and_run_testfile() nnoremap PlenaryTestFile :lua require('plenary.test_harness').test_directory(vim.fn.expand("%:p"))