diff --git a/lua/neotest-golang/cmd.lua b/lua/neotest-golang/cmd.lua index 42cc38a8..6b368006 100644 --- a/lua/neotest-golang/cmd.lua +++ b/lua/neotest-golang/cmd.lua @@ -2,7 +2,6 @@ local async = require("neotest.async") -local convert = require("neotest-golang.convert") local options = require("neotest-golang.options") local json = require("neotest-golang.json") @@ -21,51 +20,36 @@ function M.golist_data(cwd) return json.process_golist_output(output) end -function M.gotest_list_data(go_mod_folderpath, module_name) - local cmd = { "go", "test", "-v", "-json", "-list", "^Test", module_name } - local output = vim.fn.system( - "cd " .. go_mod_folderpath .. " && " .. table.concat(cmd, " ") - ) - - -- FIXME: weird... would've expected to call process_gotest_output - local json_output = json.process_golist_output(output) - - --- @type string[] - local test_names = {} - for _, v in ipairs(json_output) do - if v.Action == "output" then - --- @type string - local test_name = string.gsub(v.Output, "\n", "") - if string.match(test_name, "^Test") then - test_names = vim.list_extend( - test_names, - { convert.to_gotest_regex_pattern(test_name) } - ) - end - end +function M.sed_regexp(filepath) + if M.system_has("sed") == false then + return nil end - - return test_names + local sed_command = + string.format("sed -n '/func Test/p' %s", vim.fn.shellescape(filepath)) + local handle = io.popen(sed_command) + local result = nil + if handle ~= nil then + result = handle:read("*a") + handle:close() + end + local lines = {} + for line in string.gmatch(result, "([^\n]*)\n") do + line = string.gsub(line, "func ", "") + line = string.gsub(line, "%(.*", "") + table.insert(lines, line) + end + local regexp = "^(" .. table.concat(lines, "|") .. ")$" + return regexp end -function M.test_command_for_dir(module_name) - local go_test_required_args = { module_name } +function M.test_command_in_package(package_or_path) + local go_test_required_args = { package_or_path } local cmd, json_filepath = M.test_command(go_test_required_args) return cmd, json_filepath end -function M.test_command_for_file(module_name, test_names_regexp) - local go_test_required_args = { "-run", test_names_regexp, module_name } - local cmd, json_filepath = M.test_command(go_test_required_args) - - return cmd, json_filepath -end - -function M.test_command_for_individual_test( - test_folder_absolute_path, - test_name -) - local go_test_required_args = { test_folder_absolute_path, "-run", test_name } +function M.test_command_in_package_with_regexp(package_or_path, regexp) + local go_test_required_args = { package_or_path, "-run", regexp } local cmd, json_filepath = M.test_command(go_test_required_args) return cmd, json_filepath end @@ -73,7 +57,7 @@ end function M.test_command(go_test_required_args) --- The runner to use for running tests. --- @type string - local runner = M.fallback_to_go_test(options.get().runner) + local runner = M.runner_fallback(options.get().runner) --- The filepath to write test output JSON to, if using `gotestsum`. --- @type string | nil @@ -109,16 +93,20 @@ function M.gotestsum(go_test_required_args, json_filepath) return cmd end -function M.fallback_to_go_test(executable) - if vim.fn.executable(executable) == 0 then - vim.notify( - "Runner " .. executable .. " not found. Falling back to 'go'.", - vim.log.levels.WARN - ) +function M.runner_fallback(executable) + if M.system_has(executable) == false then options.set({ runner = "go" }) return options.get().runner end return options.get().runner end +function M.system_has(executable) + if vim.fn.executable(executable) == 0 then + vim.notify("Executable not found: " .. executable, vim.log.levels.WARN) + return false + end + return true +end + return M diff --git a/lua/neotest-golang/init.lua b/lua/neotest-golang/init.lua index 1b5a6ad0..be641ff3 100644 --- a/lua/neotest-golang/init.lua +++ b/lua/neotest-golang/init.lua @@ -69,7 +69,7 @@ function M.Adapter.build_spec(args) --- The position object, describing the current directory, file or test. --- @type neotest.Position - local pos = args.tree:data() + local pos = args.tree:data() -- NOTE: causes is not accessible by the current user! if not tree then vim.notify( @@ -100,11 +100,6 @@ function M.Adapter.build_spec(args) -- The idea here is not to have such fallbacks take place in the future, but -- while this adapter is being developed, it can be useful to have such -- functionality. - -- - -- NOTE: Right now the adapter is not yet complete, and cannot - -- handle the 'file' position type. The goal is to try to support this, - -- as it is not efficient to run all tests in a file individually, when you - -- might want to run all tests in the file using one 'go test' command. if pos.type == "dir" and pos.path == vim.fn.getcwd() then -- A runspec is to be created, based on running all tests in the given @@ -126,7 +121,7 @@ function M.Adapter.build_spec(args) end vim.notify( - "Unknown Neotest test position type, " + "Unknown Neotest position type, " .. "cannot build runspec with position type: " .. pos.type, vim.log.levels.ERROR @@ -135,9 +130,6 @@ end --- Process the test command output and result. Populate test outcome into the --- Neotest internal tree structure. ---- ---- TODO: implement parsing of 'file' position type results. ---- --- @async --- @param spec neotest.RunSpec --- @param result neotest.StrategyResult diff --git a/lua/neotest-golang/parse.lua b/lua/neotest-golang/parse.lua index b523053e..2bc0090e 100644 --- a/lua/neotest-golang/parse.lua +++ b/lua/neotest-golang/parse.lua @@ -90,9 +90,7 @@ function M.test_results(spec, result, tree) --- Test command (e.g. 'go test') status. --- @type neotest.ResultStatus local test_command_status = "skipped" - if context.dummy_test == true then - test_command_status = "skipped" - elseif result.code == 0 then + if result.code == 0 then test_command_status = "passed" else test_command_status = "failed" @@ -115,11 +113,6 @@ function M.test_results(spec, result, tree) output = test_command_output_path, } - -- if the test execution was skipped, return early - if context.dummy_test == true then - return neotest_result - end - --- Internal data structure to store test result data. --- @type table local res = M.aggregate_data(tree, gotest_output, golist_output) diff --git a/lua/neotest-golang/runspec_dir.lua b/lua/neotest-golang/runspec_dir.lua index 9ae61002..cb099d8c 100644 --- a/lua/neotest-golang/runspec_dir.lua +++ b/lua/neotest-golang/runspec_dir.lua @@ -1,5 +1,5 @@ --- Helpers to build the command and context around running all tests of ---- a Go module. +--- a Go package. local cmd = require("neotest-golang.cmd") @@ -24,16 +24,16 @@ function M.build(pos) local go_mod_folderpath = vim.fn.fnamemodify(go_mod_filepath, ":h") local golist_data = cmd.golist_data(go_mod_folderpath) - -- find the go module that corresponds to the go_mod_folderpath - local module_name = "./..." -- if no go module, run all tests at the $CWD + -- find the go package that corresponds to the go_mod_folderpath + local package_name = "./..." for _, golist_item in ipairs(golist_data) do if pos.path == golist_item.Dir then - module_name = golist_item.ImportPath + package_name = golist_item.ImportPath break end end - local test_cmd, json_filepath = cmd.test_command_for_dir(module_name) + local test_cmd, json_filepath = cmd.test_command_in_package(package_name) --- @type RunspecContext local context = { @@ -56,7 +56,7 @@ end function M.fail_fast(pos) local msg = "The selected folder must contain a go.mod file " - .. "or be a subdirectory of a Go module." + .. "or be a subdirectory of a Go package." vim.notify(msg, vim.log.levels.ERROR) --- @type RunspecContext diff --git a/lua/neotest-golang/runspec_file.lua b/lua/neotest-golang/runspec_file.lua index 3b4f02c8..3e64159b 100644 --- a/lua/neotest-golang/runspec_file.lua +++ b/lua/neotest-golang/runspec_file.lua @@ -11,7 +11,7 @@ local M = {} --- @return neotest.RunSpec | neotest.RunSpec[] | nil function M.build(pos, tree) if vim.tbl_isempty(tree:children()) then - M.fail_fast(pos) + return M.fail_fast(pos) end local go_mod_filepath = runspec_dir.find_file_upwards("go.mod", pos.path) @@ -24,13 +24,29 @@ function M.build(pos, tree) local go_mod_folderpath = vim.fn.fnamemodify(go_mod_filepath, ":h") local golist_data = cmd.golist_data(go_mod_folderpath) - -- find the go module that corresponds to the go_mod_folderpath - local module_name = "./..." -- if no go module, run all tests at the $CWD + -- find the go package that corresponds to the pos.path + local package_name = "./..." + local pos_path_filename = vim.fn.fnamemodify(pos.path, ":t") + for _, golist_item in ipairs(golist_data) do + if golist_item.TestGoFiles ~= nil then + if vim.tbl_contains(golist_item.TestGoFiles, pos_path_filename) then + package_name = golist_item.ImportPath + break + end + end + end - local test_names_regexp = - M.find_tests_in_file(pos, golist_data, go_mod_folderpath, module_name) - local test_cmd, json_filepath = - cmd.test_command_for_file(module_name, test_names_regexp) + -- use sed to find all top-level tests in pos.path + local test_cmd = nil + local json_filepath = nil + local regexp = cmd.sed_regexp(pos.path) + if regexp ~= nil then + test_cmd, json_filepath = + cmd.test_command_in_package_with_regexp(package_name, regexp) + else + -- fallback: run all tests in the package + test_cmd, json_filepath = cmd.test_command_in_package(package_name) + end --- @type RunspecContext local context = { @@ -57,8 +73,7 @@ function M.fail_fast(pos) pos_id = pos.id, pos_type = "file", golist_data = {}, -- no golist output - parse_test_results = true, - dummy_test = true, + parse_test_results = false, } --- Runspec designed for files that contain no tests. @@ -70,23 +85,4 @@ function M.fail_fast(pos) return run_spec end -function M.find_tests_in_file(pos, golist_data, go_mod_folderpath, module_name) - local pos_path_filename = vim.fn.fnamemodify(pos.path, ":t") - - for _, golist_item in ipairs(golist_data) do - if golist_item.TestGoFiles ~= nil then - if vim.tbl_contains(golist_item.TestGoFiles, pos_path_filename) then - module_name = golist_item.ImportPath - break - end - end - end - - -- FIXME: this grabs all test files from the package. We only want the one in the file. - local test_names = cmd.gotest_list_data(go_mod_folderpath, module_name) - local test_names_regexp = "^(" .. table.concat(test_names, "|") .. ")$" - - return test_names_regexp -end - return M diff --git a/lua/neotest-golang/runspec_test.lua b/lua/neotest-golang/runspec_test.lua index 2caeeddb..e1bb50c8 100644 --- a/lua/neotest-golang/runspec_test.lua +++ b/lua/neotest-golang/runspec_test.lua @@ -20,8 +20,10 @@ 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 test_cmd, json_filepath = - cmd.test_command_for_individual_test(test_folder_absolute_path, test_name) + local test_cmd, json_filepath = cmd.test_command_in_package_with_regexp( + test_folder_absolute_path, + test_name + ) local runspec_strategy = nil if strategy == "dap" then