Skip to content

Commit

Permalink
feat: run all tests in file using one 'go test' command
Browse files Browse the repository at this point in the history
  • Loading branch information
fredrikaverpil committed Jul 4, 2024
1 parent ba9216f commit a10939e
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 31 deletions.
45 changes: 39 additions & 6 deletions lua/neotest-golang/cmd.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

local async = require("neotest.async")

local convert = require("neotest-golang.convert")
local options = require("neotest-golang.options")
local json = require("neotest-golang.json")

Expand All @@ -20,6 +21,44 @@ 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, " ")
)
local json_output = json.process_golist_output(output) -- NOTE: weird... would've expected to call process_gotest_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
end

return test_names
end

function M.test_command_for_dir(module_name)
local go_test_required_args = { module_name }
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
Expand All @@ -29,12 +68,6 @@ function M.test_command_for_individual_test(
return cmd, json_filepath
end

function M.test_command_for_dir(module_name)
local go_test_required_args = { module_name }
local cmd, json_filepath = M.test_command(go_test_required_args)
return cmd, json_filepath
end

function M.test_command(go_test_required_args)
--- The runner to use for running tests.
--- @type string
Expand Down
20 changes: 18 additions & 2 deletions lua/neotest-golang/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ function M.Adapter.results(spec, result, tree)
local results = parse.test_results(spec, result, tree)
M.workaround_neotest_issue_391(result)
return results
elseif spec.context.pos_type == "file" then
-- A test command executed a file of tests and the output/status must
-- now be processed.
local results = parse.test_results(spec, result, tree)
M.workaround_neotest_issue_391(result)
return results
elseif spec.context.pos_type == "test" then
-- A test command executed a single test and the output/status must now be
-- processed.
Expand All @@ -171,8 +177,18 @@ function M.workaround_neotest_issue_391(result)
-- FIXME: once output is parsed, erase file contents, so to avoid JSON in
-- output panel. This is a workaround for now, only because of
-- https://github.com/nvim-neotest/neotest/issues/391
if result.output ~= nil then
vim.fn.writefile({ "" }, result.output)

-- NOTE: when emptying the file with vim.fn.writefil, this error was hit
-- when debugging:
-- E5560: Vimscript function must not be called in a lua loop callback
-- vim.fn.writefile({ "" }, result.output)

if result.output ~= nil then -- and vim.fn.filereadable(result.output) == 1 then
local file = io.open(result.output, "w")
if file ~= nil then
file:write("")
file:close()
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion lua/neotest-golang/json.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local M = {}
--- Process output from 'go test -json' and return an iterable table.
--- @param raw_output table
--- @return table
function M.process_gotest_output(raw_output)
function M.process_gotest_json_output(raw_output)
local jsonlines = {}
for _, line in ipairs(raw_output) do
if string.match(line, "^%s*{") then -- must start with the `{` character
Expand Down
2 changes: 1 addition & 1 deletion lua/neotest-golang/parse.lua
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function M.test_results(spec, result, tree)
raw_output = async.fn.readfile(context.test_output_json_filepath)
end

local gotest_output = json.process_gotest_output(raw_output)
local gotest_output = json.process_gotest_json_output(raw_output)

--- The 'go list -json' output, converted into a lua table.
local golist_output = context.golist_data
Expand Down
99 changes: 78 additions & 21 deletions lua/neotest-golang/runspec_file.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
--- Helpers to build the command and context around running all tests of a file.

local cmd = require("neotest-golang.cmd")
local runspec_dir = require("neotest-golang.runspec_dir")

local M = {}

--- Build runspec for a directory.
Expand All @@ -8,28 +11,82 @@ local M = {}
--- @return neotest.RunSpec | neotest.RunSpec[] | nil
function M.build(pos, tree)
if vim.tbl_isempty(tree:children()) then
--- @type RunspecContext
local context = {
pos_id = pos.id,
pos_type = "test", -- TODO: to be implemented as "file" later
golist_data = {}, -- no golist output
parse_test_results = true,
dummy_test = true,
}

--- Runspec designed for files that contain no tests.
--- @type neotest.RunSpec
local run_spec = {
command = { "echo", "No tests found in file" },
context = context,
}
return run_spec
else
-- TODO: Implement a runspec for a file of tests.
-- A bare return will delegate test execution to per-test execution, which
-- will have to do for now.
return
M.fail_fast(pos)
end

local go_mod_filepath = runspec_dir.find_file_upwards("go.mod", pos.path)
if go_mod_filepath == nil then
-- if no go.mod file was found up the directory tree, until reaching $CWD,
-- then we cannot determine the Go project root.
return M.fail_fast(pos)
end

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

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)

--- @type RunspecContext
local context = {
pos_id = pos.id,
pos_type = "file",
golist_data = golist_data,
parse_test_results = true,
test_output_json_filepath = json_filepath,
}

--- @type neotest.RunSpec
local run_spec = {
command = test_cmd,
cwd = go_mod_folderpath,
context = context,
}

return run_spec
end

function M.fail_fast(pos)
--- @type RunspecContext
local context = {
pos_id = pos.id,
pos_type = "file",
golist_data = {}, -- no golist output
parse_test_results = true,
dummy_test = true,
}

--- Runspec designed for files that contain no tests.
--- @type neotest.RunSpec
local run_spec = {
command = { "echo", "No tests found in file" },
context = context,
}
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

0 comments on commit a10939e

Please sign in to comment.