Skip to content

Commit

Permalink
feat: rebuild neotest tree, remove hacks
Browse files Browse the repository at this point in the history
This relies on AST-parsing of the *_test.go files ahead of running the
adapter.
  • Loading branch information
fredrikaverpil committed Jul 7, 2024
1 parent 89670c0 commit 6f7caf9
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 136 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ return {
| `go_test_args` | `{ "-v", "-race", "-count=1" }` | Arguments to pass into `go test`. |
| `dap_go_enabled` | `false` | Leverage [leoluz/nvim-dap-go](https://github.com/leoluz/nvim-dap-go) for debugging tests. |
| `dap_go_opts` | `{}` | Options to pass into `require("dap-go").setup()`. |
| `testify` | `false` | Enable support for [stretchr/testify](https://github.com/stretchr/testify) suites. |
| `warn_test_name_dupes` | `true` | Warn about duplicate test names within the same Go package. |
| `warn_test_not_executed` | `true` | Warn if test was not executed. |

Expand Down
67 changes: 15 additions & 52 deletions lua/neotest-golang/ast.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

local lib = require("neotest.lib")

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

local M = {}

--- Detect test names in Go *._test.go files.
--- @param file_path string
function M.detect_tests(file_path)
local test_function = [[
local test_function = [[
; query for test function
((function_declaration
name: (identifier) @test.name) (#match? @test.name "^(Test|Example)"))
Expand All @@ -23,13 +21,13 @@ function M.detect_tests(file_path)
@test.definition
]]

local test_method = [[
local test_method = [[
; query for test method
(method_declaration
name: (field_identifier) @test.name (#match? @test.name "^(Test|Example)")) @test.definition
]]

local receiver_method = [[
local receiver_method = [[
; query for receiver method, to be used as test suite namespace
(method_declaration
receiver: (parameter_list
Expand All @@ -39,7 +37,7 @@ function M.detect_tests(file_path)
(type_identifier) @namespace.name )))) @namespace.definition
]]

local table_tests = [[
local table_tests = [[
;; query for list table tests
(block
(short_var_declaration
Expand Down Expand Up @@ -139,58 +137,23 @@ function M.detect_tests(file_path)
(#eq? @test.key.name @test.key.name1))))))))
]]

local query = test_function .. test_method .. table_tests .. receiver_method
local query = test_function .. test_method .. table_tests .. receiver_method

--- Detect test names in Go *._test.go files.
--- @param file_path string
function M.detect_tests(file_path)
local opts = { nested_tests = true }

---@type neotest.Tree
local tree = lib.treesitter.parse_positions(file_path, query, opts)

-- HACK: code below for testify suite support.
-- TODO: hide functionality behind opt-in option.
local tree_with_merged_namespaces =
testify.merge_duplicate_namespaces(tree:root())
local testify_query = [[
; query
(function_declaration ; [38, 0] - [40, 1]
name: (identifier) @testify.function_name ; [38, 5] - [38, 14]
;parameters: (parameter_list ; [38, 14] - [38, 28]
; (parameter_declaration ; [38, 15] - [38, 27]
; name: (identifier) ; [38, 15] - [38, 16]
; type: (pointer_type ; [38, 17] - [38, 27]
; (qualified_type ; [38, 18] - [38, 27]
; package: (package_identifier) ; [38, 18] - [38, 25]
; name: (type_identifier))))) ; [38, 26] - [38, 27]
body: (block ; [38, 29] - [40, 1]
(expression_statement ; [39, 1] - [39, 34]
(call_expression ; [39, 1] - [39, 34]
function: (selector_expression ; [39, 1] - [39, 10]
operand: (identifier) @testify.module ; [39, 1] - [39, 6]
field: (field_identifier) @testify.run ) @testify.call ; [39, 7] - [39, 10]
arguments: (argument_list ; [39, 10] - [39, 34]
(identifier) @testify.t ; [39, 11] - [39, 12]
(call_expression ; [39, 14] - [39, 33]
function: (identifier) ; [39, 14] - [39, 17]
arguments: (argument_list ; [39, 17] - [39, 33]
(type_identifier) @testify.receiver ))))))) @testify.definition
]]

local testify_nodes = testify.run_query_on_file(file_path, testify_query)

for test_fun, data in pairs(testify_nodes) do
local function_name = nil
local receiver = nil
for _, node in ipairs(data) do
if node.name == "testify.function_name" then
function_name = node.text
end
if node.name == "testify.receiver" then
receiver = node.text
end
end
testify.add(file_path, function_name, receiver) -- FIXME: accumulates forever
if options.get().testify == true then
local tree_modified_for_testify =
testify.modify_neotest_tree(file_path, tree)
return tree_modified_for_testify
end

return tree_with_merged_namespaces
return tree
end

return M
8 changes: 7 additions & 1 deletion lua/neotest-golang/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ local options = require("neotest-golang.options")
local ast = require("neotest-golang.ast")
local runspec_dir = require("neotest-golang.runspec_dir")
local runspec_file = require("neotest-golang.runspec_file")
local runspec_namespace = require("neotest-golang.runspec_namespace")
local runspec_test = require("neotest-golang.runspec_test")
local parse = require("neotest-golang.parse")
local testify = require("neotest-golang.testify")

local M = {}

Expand Down Expand Up @@ -195,6 +195,12 @@ end
setmetatable(M.Adapter, {
__call = function(_, opts)
M.Adapter.options = options.setup(opts)

-- FIXME: not the best place to put this. Does Neotest provide a callback?
if options.get().testify == true then
testify.generate_lookup_map()
end

return M.Adapter
end,
})
Expand Down
1 change: 1 addition & 0 deletions lua/neotest-golang/options.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local opts = {
dap_go_opts = {},
warn_test_name_dupes = true,
warn_test_not_executed = true,
testify = false,

-- experimental, for now undocumented, options
runner = "go", -- or "gotestsum"
Expand Down
21 changes: 1 addition & 20 deletions lua/neotest-golang/parse.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ local async = require("neotest.async")
local options = require("neotest-golang.options")
local convert = require("neotest-golang.convert")
local json = require("neotest-golang.json")
local testify = require("neotest-golang.testify")

-- TODO: remove pos_type when properly supporting all position types.
-- and instead get this from the pos.type field.
Expand Down Expand Up @@ -193,22 +192,6 @@ function M.gather_neotest_data_and_set_defaults(tree)
return res
end

local function hack(test_name)
-- HACK: replace receiver with suite for testify.
-- TODO: place this under opt-in option.
-- TODO: could make more efficient by matching on filename first?
for filename, data in pairs(testify.get()) do
for _, entry in ipairs(data) do
-- TODO: better, more reliable matching needed
if string.match(test_name, "^" .. entry.suite .. "/") then
test_name = string.gsub(test_name, entry.suite, entry.receiver)
return test_name
end
end
end
return test_name
end

--- Decorate the internal test result data with go package and test name.
--- This is an important step, in which we figure out exactly which test output
--- belongs to which test in the Neotest position tree.
Expand Down Expand Up @@ -242,16 +225,14 @@ function M.decorate_with_go_package_and_test_name(
if gotestline.Package == golistline.ImportPath then
local pattern = convert.to_lua_pattern(folderpath)
.. "/(.-)/"
.. convert.to_lua_pattern(hack(gotestline.Test))
.. convert.to_lua_pattern(gotestline.Test)
.. "$"
match = tweaked_pos_id:find(pattern, 1, false)
if match ~= nil then
test_data.gotest_data.pkg = gotestline.Package
test_data.gotest_data.name = gotestline.Test
break
end

-- HACK: testify suites
end
if match ~= nil then
break
Expand Down
15 changes: 1 addition & 14 deletions lua/neotest-golang/runspec_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ local convert = require("neotest-golang.convert")
local options = require("neotest-golang.options")
local cmd = require("neotest-golang.cmd")
local dap = require("neotest-golang.dap")
local testify = require("neotest-golang.testify")

local M = {}

Expand All @@ -17,20 +16,8 @@ function M.build(pos, strategy)
local test_folder_absolute_path = string.match(pos.path, "(.+)/")
local golist_data = cmd.golist_data(test_folder_absolute_path)

local pos_id = pos.id

-- HACK: replace receiver with suite for testify.
-- TODO: place this under opt-in option.
for filename, data in pairs(testify.get()) do
for _, entry in ipairs(data) do
if string.match(pos_id, "::" .. entry.receiver .. "::") then
pos_id = string.gsub(pos_id, entry.receiver, entry.suite)
end
end
end

--- @type string
local test_name = convert.to_gotest_test_name(pos_id)
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_in_package_with_regexp(
Expand Down
Loading

0 comments on commit 6f7caf9

Please sign in to comment.