diff --git a/lua/neotest-golang/results_dir.lua b/lua/neotest-golang/results_dir.lua index 744cd6fa..1a36708d 100644 --- a/lua/neotest-golang/results_dir.lua +++ b/lua/neotest-golang/results_dir.lua @@ -5,17 +5,16 @@ local json = require("neotest-golang.json") local utils = require("neotest-golang.utils") --- @class InternalResult +--- @field neotest_position neotest.Position --- @field status neotest.ResultStatus ---- @field output? string[] Go test output. ---- @field short? string Shortened output string --- @field errors? neotest.Error[] ---- @field neotest_node_data neotest.Position ---- @field go_test_data GoTestData +--- @field gotest_data GoTestData --- @field duplicate_test_detected boolean --- @class GoTestData --- @field name string --- @field package string +--- @field output? string[] Go test output. local M = {} @@ -35,11 +34,11 @@ function M.results(spec, result, tree) --- Internal data structure to store test results. --- @type table - local d = M.aggregate_data(tree, gotest_output) + local res = M.aggregate_data(tree, gotest_output) - M.show_warnings(d) + M.show_warnings(res) - local neotest_results = M.to_neotest_results(spec, result, d, gotest_output) + local neotest_results = M.to_neotest_results(spec, result, res, gotest_output) -- FIXME: once output is parsed, erase file contents, so to avoid JSON in -- output panel. This is a workaround for now, only because of @@ -57,10 +56,10 @@ end --- @param gotest_output table --- @return table function M.aggregate_data(tree, gotest_output) - local d = M.gather_neotest_data_and_set_defaults(tree) - d = M.decorate_with_go_package_and_test_name(d, gotest_output) - d = M.decorate_with_go_test_results(d, gotest_output) - return d + local res = M.gather_neotest_data_and_set_defaults(tree) + res = M.decorate_with_go_package_and_test_name(res, gotest_output) + res = M.decorate_with_go_test_results(res, gotest_output) + return res end --- Generate the internal data which will be used by neotest-golang before. @@ -70,7 +69,7 @@ end function M.gather_neotest_data_and_set_defaults(tree) --- Internal data structure to store test results. --- @type table - local d = {} + local res = {} --- Table storing the name of the test (position.id) and the number of times --- it was found in the tree. @@ -82,16 +81,16 @@ function M.gather_neotest_data_and_set_defaults(tree) local pos = node:data() if pos.type == "test" then - d[pos.id] = { - status = "skipped", -- default - output = {}, -- default -- TODO: move into go_test_data - errors = {}, -- default -- TODO: move into go_test_data - neotest_node_data = pos, -- TODO: rename to neotest_position_data - go_test_data = { - name = "", -- default - package = "", -- default - }, -- default - duplicate_test_detected = false, -- default + res[pos.id] = { + status = "skipped", + errors = {}, + neotest_position = pos, + gotest_data = { + name = "", + package = "", + output = {}, + }, + duplicate_test_detected = false, } -- detect duplicate test names @@ -99,42 +98,42 @@ function M.gather_neotest_data_and_set_defaults(tree) dupes[pos.id] = 1 else dupes[pos.id] = dupes[pos.id] + 1 - d[pos.id].duplicate_test_detected = true + res[pos.id].duplicate_test_detected = true end end end - return d + return res end --- Decorate the internal results with go package and test name. --- This is an important step to associate the test results with the tree nodes --- as the 'go test' JSON output contains keys 'Package' and 'Test'. ---- @param d table +--- @param res table --- @param gotest_output table --- @return table -function M.decorate_with_go_package_and_test_name(d, gotest_output) - for pos_id in pairs(d) do +function M.decorate_with_go_package_and_test_name(res, gotest_output) + for pos_id in pairs(res) do for _, line in ipairs(gotest_output) do if line.Action == "run" and line.Test ~= nil then local folderpath = - vim.fn.fnamemodify(d[pos_id].neotest_node_data.path, ":h") + vim.fn.fnamemodify(res[pos_id].neotest_position.path, ":h") local match = nil local common_path = utils.find_common_path(line.Package, folderpath) if common_path ~= "" then - local tweaked_neotest_node_id = pos_id:gsub(" ", "_") - tweaked_neotest_node_id = tweaked_neotest_node_id:gsub('"', "") - tweaked_neotest_node_id = tweaked_neotest_node_id:gsub("::", "/") + local tweaked_pos_id = pos_id:gsub(" ", "_") + tweaked_pos_id = tweaked_pos_id:gsub('"', "") + tweaked_pos_id = tweaked_pos_id:gsub("::", "/") local combined_pattern = convert.to_lua_pattern(common_path) .. "/(.-)/" .. convert.to_lua_pattern(line.Test) .. "$" - match = tweaked_neotest_node_id:match(combined_pattern) + match = tweaked_pos_id:match(combined_pattern) end if match ~= nil then - d[pos_id].go_test_data = { + res[pos_id].gotest_data = { package = line.Package, name = line.Test, } @@ -144,34 +143,36 @@ function M.decorate_with_go_package_and_test_name(d, gotest_output) end end - return d + return res end --- Decorate the internal results with data from the 'go test' output. ---- @param d table +--- @param res table --- @param gotest_output table --- @return table -function M.decorate_with_go_test_results(d, gotest_output) - for pos_id in pairs(d) do +function M.decorate_with_go_test_results(res, gotest_output) + for pos_id in pairs(res) do for _, line in ipairs(gotest_output) do if - d[pos_id].go_test_data.package == line.Package - and d[pos_id].go_test_data.name == line.Test + res[pos_id].gotest_data.package == line.Package + and res[pos_id].gotest_data.name == line.Test then - -- record test status + -- test matched, let's record some data and decorate the internal + -- results with it. + if line.Action == "pass" then - d[pos_id].status = "passed" + res[pos_id].status = "passed" elseif line.Action == "fail" then - d[pos_id].status = "failed" + res[pos_id].status = "failed" elseif line.Action == "output" then - -- append line.Output to output field - d[pos_id].output = vim.list_extend(d[pos_id].output, { line.Output }) + res[pos_id].gotest_data.output = + vim.list_extend(res[pos_id].gotest_data.output, { line.Output }) -- determine test filename local test_filename = "_test.go" -- approximate test filename - if d[pos_id].neotest_node_data ~= nil then + if res[pos_id].neotest_position ~= nil then -- node data is available, get the exact test filename - local test_filepath = d[pos_id].neotest_node_data.path + local test_filepath = res[pos_id].neotest_position.path test_filename = vim.fn.fnamemodify(test_filepath, ":t") end @@ -183,7 +184,7 @@ function M.decorate_with_go_test_results(d, gotest_output) local message = string.match(line.Output, test_filename .. ":%d+: (.*)") if line_number ~= nil and message ~= nil then - table.insert(d[pos_id].errors, { + table.insert(res[pos_id].errors, { line = line_number - 1, -- neovim lines are 0-indexed message = message, }) @@ -193,17 +194,17 @@ function M.decorate_with_go_test_results(d, gotest_output) end end end - return d + return res end --- Show warnings. ---- @param d table +--- @param res table --- @return nil -function M.show_warnings(d) +function M.show_warnings(res) -- warn if Go package/test is missing from tree node. -- TODO: make configurable to skip this or use different log level? - for pos_id in pairs(d) do - if d[pos_id].go_test_data.name == "" then + for pos_id in pairs(res) do + if res[pos_id].gotest_data.name == "" then vim.notify( "Unable to associate go package/test with neotest tree node: " .. pos_id, vim.log.levels.WARN @@ -215,14 +216,13 @@ function M.show_warnings(d) -- warn about duplicate tests -- TODO: make debug level configurable - for pos_id in pairs(d) do - local test_data = d[pos_id] - if test_data.duplicate_test_detected == true then + for pos_id in pairs(res) do + if res[pos_id].duplicate_test_detected == true then vim.notify( "Duplicate test name detected: " - .. test_data.go_test_data.package + .. res[pos_id].gotest_data.package .. "/" - .. test_data.go_test_data.name, + .. res[pos_id].gotest_data.name, vim.log.levels.WARN ) end @@ -232,27 +232,49 @@ end --- Convert internal results to Neotest results. --- @param spec neotest.RunSpec --- @param result neotest.StrategyResult ---- @param d table +--- @param res table --- @param gotest_output table --- @return table -function M.to_neotest_results(spec, result, d, gotest_output) - --- Neotest results. +function M.to_neotest_results(spec, result, res, gotest_output) + --- Neotest result to ultimately hand back over to Neotest. --- @type table - local neotest_results = {} + local neotest_result = {} - -- populate all test results onto the Neotest format. - for pos_id in pairs(d) do - local test_data = d[pos_id] + -- populate all test results onto the Neotest result format. + for pos_id in pairs(res) do local test_output_path = vim.fs.normalize(async.fn.tempname()) - async.fn.writefile(test_data.output, test_output_path) - neotest_results[pos_id] = { - status = test_data.status, - errors = test_data.errors, + async.fn.writefile(res[pos_id].gotest_data.output, test_output_path) + neotest_result[pos_id] = { + status = res[pos_id].status, + errors = res[pos_id].errors, output = test_output_path, -- NOTE: could be slow when running many tests? } end - --- Test command (e.g. 'go test') status. + neotest_result = M.decorate_with_test_command_data( + spec, + result, + neotest_result, + gotest_output + ) + + return neotest_result +end + +--- Decorate the Neotest results with the ultimate status code of the command +--- which executed, along with its full output. +--- @param spec neotest.RunSpec +--- @param result neotest.StrategyResult +--- @param neotest_results neotest.Result +--- @param gotest_output table +--- @return table +function M.decorate_with_test_command_data( + spec, + result, + neotest_results, + gotest_output +) + --- The status code of the command which executed all tests in the directory. --- @type neotest.ResultStatus local test_command_status = "skipped" if result.code == 0 then @@ -261,7 +283,8 @@ function M.to_neotest_results(spec, result, d, gotest_output) test_command_status = "failed" end - --- Full 'go test' output (parsed from JSON). + --- The full output associated with the command which executed all tests in + --- the directory. --- @type table local full_output = {} local test_command_output_path = vim.fs.normalize(async.fn.tempname()) @@ -272,7 +295,8 @@ function M.to_neotest_results(spec, result, d, gotest_output) end async.fn.writefile(full_output, test_command_output_path) - -- register properties on the directory node that was run + -- Register the status and output of the command which executed all tests + -- in the directory. neotest_results[spec.context.id] = { status = test_command_status, output = test_command_output_path,