Skip to content

Commit

Permalink
feat: execute all tests in dir
Browse files Browse the repository at this point in the history
  • Loading branch information
fredrikaverpil committed Jun 11, 2024
1 parent 35d0a3c commit 317ba41
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 9 deletions.
13 changes: 11 additions & 2 deletions lua/neotest-golang/convert.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ 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.
-- NOTE: double quotes must be removed from the string matching against.

---@param go_test_name string
---@return string
function M.to_neotest_test_name_pattern(go_test_name)
Expand All @@ -32,14 +34,21 @@ function M.to_neotest_test_name_pattern(go_test_name)
-- Replace / with ::
test_name = test_name:gsub("/", "::")

-- NOTE: double quotes are removed from the string we match against.

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

-- Mark the end of the test name pattern
test_name = test_name .. "$"

-- Percentage sign must be escaped
test_name = test_name:gsub("%%", "%%%%")

-- Literal brackets and parantheses must be escaped
test_name = test_name:gsub("%[", "%%[")
test_name = test_name:gsub("%]", "%%]")
test_name = test_name:gsub("%(", "%%(")
test_name = test_name:gsub("%)", "%%)")

return test_name
end

Expand Down
21 changes: 16 additions & 5 deletions lua/neotest-golang/init.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
local _ = require("neotest")

local options = require("neotest-golang.options")
local discover_positions = require("neotest-golang.discover_positions")
local runspec_dir = require("neotest-golang.runspec_dir")
local runspec_test = require("neotest-golang.runspec_test")
local results_dir = require("neotest-golang.results_dir")
local results_test = require("neotest-golang.results_test")
local utils = require("neotest-golang.utils")

Expand Down Expand Up @@ -74,12 +78,10 @@ function M.Adapter.build_spec(args)

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

return -- delegate test execution to per-test execution
return runspec_dir.build(pos)
elseif pos.type == "dir" then
-- Sub-directory

return -- delegate test execution to per-test execution
return runspec_dir.build(pos)
elseif pos.type == "file" then
-- Single file

Expand All @@ -102,6 +104,8 @@ function M.Adapter.build_spec(args)
-- to compile. This approach is too brittle, and therefore this mode is not
-- supported. Instead, the tests of a file are run as if pos.typ == "test".

vim.notify("Would've executed a file: " .. pos.path)

return -- delegate test execution to per-test execution
end
elseif pos.type == "test" then
Expand All @@ -121,7 +125,14 @@ end
---@param tree neotest.Tree
---@return table<string, neotest.Result>
function M.Adapter.results(spec, result, tree)
return results_test.results_test(spec, result, tree)
if spec.context.test_type == "dir" then
return results_dir.results(spec, result, tree)
elseif spec.context.test_type == "test" then
return results_test.results(spec, result, tree)
end

vim.notify("Error: [results] unknown test type: " .. spec.context.test_type)
return {}
end

setmetatable(M.Adapter, {
Expand Down
142 changes: 142 additions & 0 deletions lua/neotest-golang/results_dir.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
local async = require("neotest.async")

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

local M = {}

---@param spec neotest.RunSpec
---@param result neotest.StrategyResult
---@param tree neotest.Tree
function M.results(spec, result, tree)
-- print(vim.inspect(spec))
-- print(vim.inspect(result))
-- print(vim.inspect(tree))

---@type neotest.ResultStatus
local result_status = "skipped"
if result.code == 0 then
result_status = "passed"
else
result_status = "failed"
end

---@type table
local raw_output = async.fn.readfile(result.output)
---@type List
local test_result = {}
---@type List<table>
local jsonlines = json.process_json(raw_output)

---@type table<string, neotest.Result>
local results = {}
-- results[spec.context.id] = {
-- status = result_status,
-- output = parsed_output_path,
-- errors = errors,
--
-- -- internal fields (not used by neotest)
-- _test = test_name,
-- }

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

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)
end

if
(line.Action == "pass" or line.Action == "fail") and line.Test ~= nil
then
local test_name_pattern = convert.to_neotest_test_name_pattern(line.Test)
for _, node in tree:iter_nodes() do
local node_data = node:data()
-- workaround, since we cannot know where double quotes might appear
local tweaked_node_data_id = node_data.id:gsub('"', "")

if
string.find(node_data.path, spec.context.id, init, is_plain)
and string.find(
tweaked_node_data_id,
test_name_pattern,
init,
is_pattern
)
then
if results[node_data.id] == nil then
results[node_data.id] = {
status = line.Action .. "ed", -- TODO: fix this
errors = {},
_test_name = line.Test,
}
break -- do not keep on iterating neotest nodes
else
vim.notify(
"OOPS, ALREADY REGISTERED: " .. node_data.id,
vim.log.levels.WARN
)
end
else
-- Loads of iterations here
end
end
end
end

-- record errors
for _, res in pairs(results) do
if res.status == "failed" then
for _, line in ipairs(jsonlines) do
if line.Action == "output" and line.Test == res._test_name then
---@type string
local matched_line_number = string.match(line.Output, ":(%d+):")
if matched_line_number ~= nil then
---@type number | nil
local line_number = tonumber(matched_line_number)

---@type string
local message = string.match(line.Output, ":%d+: (.*)")

if line_number ~= nil then
-- log the error along with its line number (for diagnostics)
table.insert(res.errors, {
line = line_number - 1, -- neovim lines are 0-indexed
message = message,
})
break -- avoid further iterations
end
end
end
end
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)

-- set output on all tests to the test execution results
for _, res in pairs(results) do
res.output = parsed_output_path
end

-- 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)

-- register output on the directory's node data id
results[spec.context.id] = {
status = result_status,
output = parsed_output_path,
}

return results
end

return M
2 changes: 1 addition & 1 deletion lua/neotest-golang/results_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ local M = {}
---@param result neotest.StrategyResult
---@param tree neotest.Tree
---@return table<string, neotest.Result>
function M.results_test(spec, result, tree)
function M.results(spec, result, tree)
if spec.context.skip then
---@type table<string, neotest.Result>
local results = {}
Expand Down
87 changes: 87 additions & 0 deletions lua/neotest-golang/runspec_dir.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
local _ = require("neotest") -- fix LSP errors

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

local M = {}

--- Build runspec for a directory.
---@param pos neotest.Position
---@return neotest.RunSpec
function M.build(pos)
-- Strategy:
-- 1. Find the go.mod file from pos.path.
-- 2. Run `go test` from the directory containing the go.mod file.
-- 3. Use the relative path from the go.mod file to pos.path as the test pattern.

local go_mod_filepath = M.find_file_upwards("go.mod", pos.path)
local go_mod_folderpath = vim.fn.fnamemodify(go_mod_filepath, ":h")
local cwd = go_mod_folderpath

-- calculate the relative path to pos.path from cwd
local relative_path = M.remove_base_path(cwd, pos.path)
local test_pattern = "./" .. relative_path .. "/..."

return M.build_dir_test_runspec(pos, cwd, test_pattern)
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)
local gotest = {
"go",
"test",
"-json",
}

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

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

---@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

return M
8 changes: 7 additions & 1 deletion tests/go/testname_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestNames(t *testing.T) {
}
})

t.Run("Comma , and ' are ok to use", func(t *testing.T) {
t.Run("Comma , and apostrophy ' are ok to use", func(t *testing.T) {
if Add(1, 2) != 3 {
t.Fail()
}
Expand All @@ -21,4 +21,10 @@ func TestNames(t *testing.T) {
t.Fail()
}
})

t.Run("Percentage sign like 50% is ok", func(t *testing.T) {
if Add(1, 2) != 3 {
t.Fail()
}
})
}

0 comments on commit 317ba41

Please sign in to comment.