Skip to content

Commit

Permalink
feat: support directory (module) execution
Browse files Browse the repository at this point in the history
  • Loading branch information
fredrikaverpil committed Jun 3, 2024
1 parent 2e42066 commit 92efb71
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 19 deletions.
23 changes: 23 additions & 0 deletions lua/neotest-golang/convert.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,27 @@ function M.to_gotest_test_name(pos_id)
return test_name
end

-- Converts the `go test` command test name into Neotest node test name format.
-- Note that a pattern can returned, not the exact test name, so to support
-- escaped quotes etc.
---@param go_test_name string
---@return string
function M.to_neotest_test_name_pattern(go_test_name)
-- construct the test name
local test_name = go_test_name
-- Add :: before the test name
test_name = "::" .. test_name
-- Replace / with ::
test_name = test_name:gsub("/", "::")

-- TODO: handle this, but the other way around:
-- Remove double quotes (single quotes are supported)
-- test_name = test_name:gsub('"', "")

-- Replace _ with space
test_name = test_name:gsub("_", " ")

return test_name
end

return M
227 changes: 208 additions & 19 deletions lua/neotest-golang/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -156,31 +156,51 @@ function M.Adapter.build_spec(args)
return
end

if pos.type == "dir" and pos.path == vim.fn.getcwd() then
-- Test suite

return -- delegate test execution to per-test execution

-- NOTE: could potentially run 'go test' on the whole directory, to make
-- tests go a lot faster, but would come with the added complexity of
-- having to traverse the node tree manually and set statuses accordingly.
-- I'm not sure it's worth it...
elseif pos.type == "dir" then
vim.notify("pos.type=" .. pos.type)

-- if pos.type == "dir" and pos.path == vim.fn.getcwd() then
-- -- Test suite
--
-- return -- delegate test execution to per-test execution
--
-- -- NOTE: could potentially run 'go test' on the whole directory, to make
-- -- tests go a lot faster, but would come with the added complexity of
-- -- having to traverse the node tree manually and set statuses accordingly.
-- -- I'm not sure it's worth it...

if pos.type == "dir" then
-- Sub-directory

return -- delegate test execution to per-test execution

-- NOTE: could potentially run 'go test' on the whole file, to make
-- tests go a lot faster, but would come with the added complexity of
-- having to traverse the node tree manually and set statuses accordingly.
-- I'm not sure it's worth it...
--
-- ---@type string
-- local relative_test_folderpath = vim.fn.fnamemodify(pos.path, ":~:.")
-- ---@type string
-- local relative_test_folderpath_go = "./"
-- .. relative_test_folderpath
-- .. "/..."

---@type string
local relative_test_folderpath = vim.fn.fnamemodify(pos.path, ":~:.")
---@type string
local relative_test_folderpath_go = "./"
.. relative_test_folderpath
.. "/..."

-- pos.path = /Users/fredrik/code/public/neotest-golang/tests/go/subfolder
vim.notify("Full path to folder: " .. pos.path)

local go_mod_filepath = M.find_file_upwards("go.mod", pos.path)

local go_mod_folderpath = vim.fn.fnamemodify(go_mod_filepath, ":h")

-- cwd = /Users/fredrik/code/public/neotest-golang/tests/go
local cwd = go_mod_folderpath
vim.notify("cwd: " .. cwd)

-- calculate the relative path to pos.path from cwd
local relative_path = M.remove_base_path(cwd, pos.path)
vim.notify("relative path: " .. relative_path)

local test_pattern = "./" .. relative_path .. "/..."

return M.build_dir_test_runspec(pos, cwd, test_pattern)
elseif pos.type == "file" then
-- Single file

Expand All @@ -192,6 +212,7 @@ function M.Adapter.build_spec(args)
context = {
id = pos.id,
skip = true,
test_type = "test", -- TODO: to be implemented as "file" later
},
}
return run_spec
Expand Down Expand Up @@ -219,6 +240,108 @@ end
---@param tree neotest.Tree
---@return table<string, neotest.Result>
function M.Adapter.results(spec, result, tree)
if spec.context.test_type == "dir" then
return M.results_dir(spec, result, tree)
elseif spec.context.test_type == "test" then
return M.results_test(spec, result, tree)
end

vim.notify("ERROR: unknown test type: " .. spec.context.test_type)
return {}
end

---@param spec neotest.RunSpec
---@param result neotest.StrategyResult
---@param tree neotest.Tree
function M.results_dir(spec, result, tree)
print("PARSE DIR RESULTS")
print(vim.inspect(spec))
-- print(vim.inspect(result))
-- print(vim.inspect(tree))

---@type table
local raw_output = async.fn.readfile(result.output)
---@type List
local test_result = {}
---@type neotest.Error[]
local errors = {}
---@type List<table>
local jsonlines = M.process_json(raw_output)

---@type table<string, neotest.Result>
local results = {}
-- results[spec.context.id] = {
-- status = result_status,
-- output = parsed_output_path,
-- errors = errors,
-- }

-- TODO: populate the status code on the spec.context.id

-- string.find options
local init = 1
local is_plain = true
local is_pattern = false

-- print(vim.inspect(jsonlines))

for _, line in ipairs(jsonlines) do
if line.Action == "output" and line.Output ~= nil then
-- record output, prints to output panel
table.insert(test_result, line.Output) -- TODO: refactor
end

-- if line contains "--- PASS"
if line.Action == "output" and string.match(line.Output, "--- PASS:") then
local go_test_name =
string.match(line.Output, "--- PASS: (.*) %(%d+%.%d+s%)")
if go_test_name ~= nil then
local neotest_test_name =
convert.to_neotest_test_name_pattern(go_test_name)

for _, node in tree:iter_nodes() do
local node_data = node:data()

if
string.find(node_data.path, spec.context.id, init, is_plain)
and string.find(node_data.id, neotest_test_name, init, is_pattern)
then
-- vim.notify(vim.inspect(node_data))

-- TODO: refactor how we populate status, output and errors
results[node_data.id] = {
status = "passed",
errors = {},
}

-- local expandable = #node:children() > 0
-- if expandable then
-- vim.notify("EXPANDABLE")
-- vim.notify(vim.inspect(node:children()))
-- end
end
end
end
end

-- if result.code ~= 0 and line.Output ~= nil then
-- -- record an error
-- 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)

-- 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
vim.fn.writefile({ "" }, result.output)

return results
end

function M.results_test(spec, result, tree)
if spec.context.skip then
---@type table<string, neotest.Result>
local results = {}
Expand Down Expand Up @@ -300,6 +423,71 @@ function M.Adapter.results(spec, result, tree)
return results
end

function M.find_file_upwards(filename, start_path)
local scan = require("plenary.scandir")
local cwd = vim.fn.getcwd() -- get the current working directory
local found_filepath = nil
while start_path ~= cwd do
local files = scan.scan_dir(
start_path,
{ search_pattern = filename, hidden = true, depth = 1 }
)
if #files > 0 then
found_filepath = files[1]
break
end
start_path = vim.fn.fnamemodify(start_path, ":h") -- go up one directory
end
return found_filepath
end

function M.remove_base_path(base_path, target_path)
if string.find(target_path, base_path, 1, true) == 1 then
return string.sub(target_path, string.len(base_path) + 2)
end

return target_path
end

--- Build runspec for a directory of tests
---@param pos neotest.Position
---@param relative_test_folderpath_go string
---@return neotest.RunSpec
function M.build_dir_test_runspec(pos, cwd, test_pattern)
vim.notify("Dir test runspec")

local gotest = {
"go",
"test",
"-json",
}

---@type table
local go_test_args = {
test_pattern,
}

local combined_args =
vim.list_extend(vim.deepcopy(M.Adapter._go_test_args), go_test_args)
local gotest_command = vim.list_extend(vim.deepcopy(gotest), combined_args)

print("Command:")
print(vim.inspect(gotest_command))

---@type neotest.RunSpec
local run_spec = {
command = gotest_command,
cwd = cwd,
context = {
id = pos.id,
test_filepath = pos.path,
test_type = "dir",
},
}

return run_spec
end

--- Build runspec for a single test
---@param pos neotest.Position
---@param strategy string
Expand Down Expand Up @@ -334,6 +522,7 @@ function M.build_single_test_runspec(pos, strategy)
context = {
id = pos.id,
test_filepath = pos.path,
test_type = "test",
},
}

Expand Down

0 comments on commit 92efb71

Please sign in to comment.