diff --git a/lua/neotest-golang/options.lua b/lua/neotest-golang/options.lua index 9e8f7f2..3bd428a 100644 --- a/lua/neotest-golang/options.lua +++ b/lua/neotest-golang/options.lua @@ -5,11 +5,9 @@ local M = {} local opts = { - go_test_args = { - "-v", - "-race", - "-count=1", - }, + runner = "gotestsum", -- or "gotestsum" + go_test_args = { "-v", "-race", "-count=1" }, + gotestsum_args = { "--format=standard-verbose" }, dap_go_enabled = false, dap_go_opts = {}, warn_test_name_dupes = true, diff --git a/lua/neotest-golang/parse.lua b/lua/neotest-golang/parse.lua index d796e76..fa3a8ae 100644 --- a/lua/neotest-golang/parse.lua +++ b/lua/neotest-golang/parse.lua @@ -32,9 +32,14 @@ function M.results(spec, result, tree) --- @type neotest.Position local pos = tree:data() - --- The raw output from the 'go test -json' command. + --- The raw output from the test command. --- @type table - local raw_output = async.fn.readfile(result.output) + local raw_output = {} + if options.get().runner == "go" then + raw_output = async.fn.readfile(result.output) + elseif options.get().runner == "gotestsum" then + raw_output = async.fn.readfile(spec.context.jsonfile) + end --- The 'go test' JSON output, converted into a lua table. --- @type table diff --git a/lua/neotest-golang/results_test.lua b/lua/neotest-golang/results_test.lua new file mode 100644 index 0000000..39e64e7 --- /dev/null +++ b/lua/neotest-golang/results_test.lua @@ -0,0 +1,118 @@ +local async = require("neotest.async") + +local options = require("neotest-golang.options") +local convert = require("neotest-golang.convert") +local json = require("neotest-golang.json") + +local M = {} + +--- @async +--- @param spec neotest.RunSpec +--- @param result neotest.StrategyResult +--- @param tree neotest.Tree +--- @return table +function M.results(spec, result, tree) + ---@type table + local results = {} + results[spec.context.id] = { + ---@type neotest.ResultStatus + status = "skipped", -- default value + } + + if spec.context.skip then + return results + end + + --- @type boolean + local no_tests_to_run = false + + --- The raw output from the test command. + --- @type table + local raw_output = {} + if options.get().runner == "go" then + raw_output = async.fn.readfile(result.output) + elseif options.get().runner == "gotestsum" then + raw_output = async.fn.readfile(spec.context.jsonfile) + end + + --- @type string + local test_filepath = spec.context.test_filepath + local test_filename = vim.fn.fnamemodify(test_filepath, ":t") + --- @type table + local test_result = {} + --- @type neotest.Error[] + local errors = {} + + --- @type table + local gotest_output = json.process_gotest_output(raw_output) + + for _, line in ipairs(gotest_output) do + if line.Action == "output" and line.Output ~= nil then + -- record output, prints to output panel + table.insert(test_result, line.Output) + + -- if test was not run, mark it as skipped + + -- if line contains "no test files" or "no tests to run", mark as skipped + if string.match(line.Output, "no tests to run") then + no_tests_to_run = true + end + end + + -- record an error + if result.code ~= 0 and line.Output ~= nil then + ---@type string + local matched_line_number = + string.match(line.Output, test_filename .. ":(%d+):") + + if matched_line_number ~= nil then + -- attempt to parse the line number... + ---@type number | nil + local line_number = tonumber(matched_line_number) + + if line_number ~= nil then + -- log the error along with its line number (for diagnostics) + + ---@type string + local message = string.match(line.Output, ":%d+: (.*)") + + ---@type neotest.Error + local error = { + message = message, + line = line_number - 1, -- neovim lines are 0-indexed + } + table.insert(errors, error) + end + end + end + end + + if no_tests_to_run then + if options.get().warn_test_not_executed == true then + vim.notify( + "Could not execute test: " + .. convert.to_gotest_test_name(spec.context.id), + vim.log.levels.WARN + ) + end + else + -- assign status code, as long as the test was found + if result.code == 0 then + results[spec.context.id].status = "passed" + else + results[spec.context.id].status = "failed" + end + end + + -- write json_decoded to file + local parsed_output_path = vim.fs.normalize(async.fn.tempname()) + async.fn.writefile(test_result, parsed_output_path) + + ---@type table + results[spec.context.id].output = parsed_output_path + results[spec.context.id].errors = errors + + return results +end + +return M diff --git a/lua/neotest-golang/runspec_dir.lua b/lua/neotest-golang/runspec_dir.lua index 7e8974d..515e5c3 100644 --- a/lua/neotest-golang/runspec_dir.lua +++ b/lua/neotest-golang/runspec_dir.lua @@ -3,6 +3,7 @@ local options = require("neotest-golang.options") local json = require("neotest-golang.json") +local async = require("neotest.async") local M = {} @@ -114,22 +115,45 @@ end --- @param module_name string --- @return neotest.RunSpec | neotest.RunSpec[] | nil function M.build_dir_test_runspec(pos, cwd, golist_output, module_name) - local gotest = { - "go", - "test", - "-json", - } - --- @type table local required_go_test_args = { module_name, } - local combined_args = vim.list_extend( - vim.deepcopy(options.get().go_test_args), - required_go_test_args - ) - local gotest_command = vim.list_extend(vim.deepcopy(gotest), combined_args) + local gotest_command = {} + local jsonfile = "" + + if options.get().runner == "go" then + local gotest = { + "go", + "test", + "-json", + } + + local combined_args = vim.list_extend( + vim.deepcopy(options.get().go_test_args), + required_go_test_args + ) + gotest_command = vim.list_extend(vim.deepcopy(gotest), combined_args) + elseif options.get().runner == "gotestsum" then + jsonfile = vim.fs.normalize(async.fn.tempname()) + local gotest = { "gotestsum" } + local gotestsum_json = { + "--jsonfile=" .. jsonfile, + "--", + } + local gotest_args = vim.list_extend( + vim.deepcopy(options.get().go_test_args), + required_go_test_args + ) + + local cmd = + vim.list_extend(vim.deepcopy(gotest), options.get().gotestsum_args) + cmd = vim.list_extend(vim.deepcopy(cmd), gotestsum_json) + cmd = vim.list_extend(vim.deepcopy(cmd), gotest_args) + + gotest_command = cmd + end --- @type neotest.RunSpec local run_spec = { @@ -143,6 +167,10 @@ function M.build_dir_test_runspec(pos, cwd, golist_output, module_name) }, } + if jsonfile ~= nil then + run_spec.context.jsonfile = jsonfile + end + return run_spec end diff --git a/lua/neotest-golang/runspec_test.lua b/lua/neotest-golang/runspec_test.lua index 0fdeaca..f922b95 100644 --- a/lua/neotest-golang/runspec_test.lua +++ b/lua/neotest-golang/runspec_test.lua @@ -3,6 +3,7 @@ local convert = require("neotest-golang.convert") local options = require("neotest-golang.options") local json = require("neotest-golang.json") +local async = require("neotest.async") local M = {} @@ -33,20 +34,43 @@ function M.build(pos, strategy) local test_name = convert.to_gotest_test_name(pos.id) test_name = convert.to_gotest_regex_pattern(test_name) - local gotest = { - "go", - "test", - "-json", - } - --- @type table local required_go_test_args = { test_folder_absolute_path, "-run", test_name } - local combined_args = vim.list_extend( - vim.deepcopy(options.get().go_test_args), - required_go_test_args - ) - local gotest_command = vim.list_extend(vim.deepcopy(gotest), combined_args) + local gotest_command = {} + local jsonfile = "" + + if options.get().runner == "go" then + local gotest = { + "go", + "test", + "-json", + } + + local combined_args = vim.list_extend( + vim.deepcopy(options.get().go_test_args), + required_go_test_args + ) + gotest_command = vim.list_extend(vim.deepcopy(gotest), combined_args) + elseif options.get().runner == "gotestsum" then + jsonfile = vim.fs.normalize(async.fn.tempname()) + local gotest = { "gotestsum" } + local gotestsum_json = { + "--jsonfile=" .. jsonfile, + "--", + } + local gotest_args = vim.list_extend( + vim.deepcopy(options.get().go_test_args), + required_go_test_args + ) + + local cmd = + vim.list_extend(vim.deepcopy(gotest), options.get().gotestsum_args) + cmd = vim.list_extend(vim.deepcopy(cmd), gotestsum_json) + cmd = vim.list_extend(vim.deepcopy(cmd), gotest_args) + + gotest_command = cmd + end --- @type neotest.RunSpec local run_spec = { @@ -60,6 +84,10 @@ function M.build(pos, strategy) }, } + if jsonfile ~= nil then + run_spec.context.jsonfile = jsonfile + end + -- set up for debugging of test if strategy == "dap" then run_spec.strategy = M.get_dap_config(test_name)