-- run `go test` local M = {} local utils = require("go.utils") local log = utils.log local empty = utils.empty local ginkgo = require("go.ginkgo") local getopt = require("go.alt_getopt") local vfn = vim.fn local long_opts = { verbose = "v", compile = "c", coverage = "C", tags = "t", bench = "b", select = "s", floaterm = "F", } local sep = require("go.utils").sep() local short_opts = "vcC:t:bsF" local bench_opts = { "-benchmem", "-cpuprofile", "profile.out" } M.efm = function() -- local indent = [[%\\%( %\\)]] local efm = [[%-G=== RUN %.%#]] efm = efm .. [[,%-G" .. indent .. "%#--- PASS: %.%#]] efm = efm .. [[,%G--- FAIL: %\\%(Example%\\)%\\@=%m (%.%#)]] efm = efm .. [[,%G" .. indent .. "%#--- FAIL: %m (%.%#)]] efm = efm .. [[,%A" .. indent .. "%\\+%[%^:]%\\+: %f:%l: %m]] efm = efm .. [[,%+Gpanic: test timed out after %.%\\+]] efm = efm .. ",%+Afatal error: %.%# [recovered]" efm = efm .. [[,%+Afatal error: %.%#]] efm = efm .. [[,%+Apanic: %.%#]] -- exit efm = efm .. ",%-Cexit status %[0-9]%\\+" efm = efm .. ",exit status %[0-9]%\\+" -- failed lines efm = efm .. ",%-CFAIL%\\t%.%#" -- compiling error efm = efm .. ",%A%f:%l:%c: %m" efm = efm .. ",%A%f:%l: %m" efm = efm .. ",%-C%.%#" efm = efm .. ",%-G%.%#" efm = string.gsub(efm, " ", [[\ ]]) log(efm) return efm end -- return "-tags=tag1,tag2" M.get_build_tags = function(args) -- local tags = "-tags" args = args or {} local tags = {} if _GO_NVIM_CFG.build_tags ~= "" then tags = { _GO_NVIM_CFG.build_tags } end local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts) if optarg["t"] then table.insert(tags, optarg["t"]) end local rt = utils.get_build_tags() if not utils.empty(rt) then vim.list_extend(tags, rt) end if #tags > 0 then return "-tags=" .. table.concat(tags, ","), reminder end end local function richgo(cmd) if cmd[1] == "go" and vfn.executable("richgo") then cmd[1] = "richgo" end return cmd end local function get_test_filebufnr() local fn = vfn.expand("%") log(fn) local bufnr = vim.api.nvim_get_current_buf() if not fn:find("test%.go$") then fn = require("go.alternate").alternate() fn = vfn.fnamemodify(fn, ":p") -- expand to full path local uri = vim.uri_from_fname(fn) bufnr = vim.uri_to_bufnr(uri) log(fn, bufnr, uri) if not vim.api.nvim_buf_is_loaded(bufnr) then vfn.bufload(bufnr) end end return bufnr end -- {-c: compile, -v: verbose, -t: tags, -b: bench, -s: select} local function run_test(path, args) log(args) local compile = false local bench = false local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts) if optarg["c"] then path = utils.rel_path(true) -- vfn.expand("%:p:h") can not resolve releative path compile = true end if optarg["b"] then bench = true end if next(reminder) then path = reminder[1] end local test_runner = _GO_NVIM_CFG.go if _GO_NVIM_CFG.test_runner ~= test_runner then test_runner = _GO_NVIM_CFG.test_runner require("go.install").install(test_runner) end local tags = M.get_build_tags(args) log(tags) local cmd = {} local run_in_floaterm = optarg["F"] or _GO_NVIM_CFG.run_in_floaterm if run_in_floaterm then table.insert(cmd, test_runner or "go") table.insert(cmd, "test") end if _GO_NVIM_CFG.verbose_tests then table.insert(cmd, "-v") end if not empty(tags) then cmd = vim.list_extend(cmd, { tags }) end if optarg["C"] then table.insert(cmd, "-coverprofile=" .. optarg["C"]) end if not empty(reminder) then cmd = vim.list_extend(cmd, reminder) end if compile == true then if path ~= "" then table.insert(cmd, "-c") table.insert(cmd, path) end elseif bench == true then if path ~= "" then table.insert(cmd, "-bench=" .. path) else table.insert(cmd, "-bench=.") end vim.list_extend(cmd, bench_opts) else if path ~= "" then table.insert(cmd, path) else local argsstr = "." .. utils.sep() .. "..." table.insert(cmd, argsstr) end end utils.log(cmd, args) if run_in_floaterm then local term = require("go.term").run cmd = richgo(cmd) log(cmd) term({ cmd = cmd, autoclose = false }) return cmd end vim.cmd([[setl makeprg=]] .. _GO_NVIM_CFG.go .. [[\ test]]) utils.log("test cmd", cmd) return require("go.asyncmake").make(unpack(cmd)) end M.test = function(...) local args = { ... } log(args) local test_opts = { verbose = "v", compile = "c", coverage = "C", tags = "t", bench = "b", floaterm = "F", nearest = "n", file = "f", package = "p", } local test_short_opts = "vcC:t:bsfnpF" local optarg, _, reminder = getopt.get_opts(args, test_short_opts, test_opts) vfn.setqflist({}) if optarg["n"] then --nearest optarg["n"] = nil local opts = getopt.rebuid_args(optarg, reminder) or {} return M.test_func(unpack(opts)) end if optarg["f"] then -- currentfile optarg["f"] = nil local opts = getopt.rebuid_args(optarg, reminder) or {} return M.test_file(unpack(opts)) end if optarg["p"] then -- current package optarg["p"] = nil local opts = getopt.rebuid_args(optarg, reminder) or {} return M.test_package(unpack(opts)) end local workfolder = utils.work_path() if workfolder == nil then workfolder = "." end local fpath = workfolder .. utils.sep() .. "..." if #reminder > 0 then fpath = reminder[1] end utils.log("fpath :" .. fpath) run_test(fpath, args) end M.test_suit = function(...) local args = { ... } log(args) local workfolder = utils.work_path() utils.log(args) local fpath = workfolder .. utils.sep() .. "..." utils.log("fpath" .. fpath) run_test(fpath, args) end M.test_package = function(...) local args = { ... } log(args) local fpath = "." .. sep .. vfn.fnamemodify(vfn.expand("%:h"), ":.") .. sep .. "..." utils.log("fpath: " .. fpath) return run_test(fpath, args) end M.get_test_func_name = function() local row, col = unpack(vim.api.nvim_win_get_cursor(0)) row, col = row, col + 1 local ns = require("go.ts.go").get_func_method_node_at_pos(row, col) if empty(ns) then return nil end if ns == nil or ns.name == nil then return nil end if not string.find(ns.name, "[T|t]est") then -- not in a test function local fns = M.get_testfunc() for _, fn in ipairs(fns) do log(fn, ns.name) if string.find(fn:lower(), ns.name:lower()) then ns = { name = fn } return ns end end end return ns end --options {s:select, F: floaterm} M.test_func = function(...) local args = { ... } log(args) local ns = M.get_test_func_name() if empty(ns) then return M.select_tests() end local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts) local tags = M.get_build_tags(args, get_test_filebufnr()) utils.log("parnode" .. vim.inspect(ns)) local test_runner = _GO_NVIM_CFG.go if _GO_NVIM_CFG.test_runner ~= "go" then require("go.install").install(test_runner) test_runner = _GO_NVIM_CFG.test_runner if test_runner == "ginkgo" then ginkgo.test_func(...) end end local run_flags = "-run" local cmd = {} local run_in_floaterm = optarg["F"] or _GO_NVIM_CFG.run_in_floaterm if run_in_floaterm then table.insert(cmd, test_runner) table.insert(cmd, "test") end if optarg["s"] then return M.select_tests() end if _GO_NVIM_CFG.verbose_tests and _GO_NVIM_CFG.test_runner == "go" then table.insert(cmd, "-v") end if tags and tags ~= "" then table.insert(cmd, tags) end if ns == nil or ns.name == nil then return end if ns.name:find("Bench") then local bench = "-bench=" .. ns.name table.insert(cmd, bench) vim.list_extend(cmd, bench_opts) else table.insert(cmd, run_flags) table.insert(cmd, [['^]] .. ns.name .. [[$']]) end local fpath = "." .. sep .. vfn.fnamemodify(vfn.expand("%:h"), ":.") table.insert(cmd, fpath) if test_runner == "dlv" then cmd = { "dlv", "test", fpath, "--", "-test.run", "^" .. ns.name } local term = require("go.term").run term({ cmd = cmd, autoclose = false }) return end if run_in_floaterm then utils.log(cmd) local term = require("go.term").run cmd = richgo(cmd) term({ cmd = cmd, autoclose = false }) return end vim.list_extend(cmd, reminder) vim.cmd([[setl makeprg=]] .. test_runner .. [[\ test]]) -- set_efm() utils.log("test cmd", cmd) return require("go.asyncmake").make(unpack(cmd)) end M.test_file = function(...) local args = { ... } log(args) -- require sed -- local testcases = [[sed -n 's/func.*\(Test.*\)(.*/\1/p' | xargs | sed 's/ /\\\|/g']] -- local fpath = vfn.expand("%:p") local fpath = "." .. sep .. vfn.fnamemodify(vfn.expand("%:p"), ":.") -- utils.log(args) local cmd = [[cat ]] .. fpath .. [[| sed -n 's/func.*\(Test.*\)(.*/\1/p' | xargs | sed 's/ /\\|/g']] -- TODO maybe with treesitter or lsp list all functions in current file and regex with Test if vfn.executable("sed") == 0 then M.test_package(...) return end local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts) local run_in_floaterm = optarg["F"] or _GO_NVIM_CFG.run_in_floaterm local tests = vfn.systemlist(cmd) if vim.v.shell_error ~= 0 then utils.warn("iferr failed" .. vim.inspect(tests)) return end utils.log(cmd, tests) tests = tests[1] if vfn.empty(tests) == 1 then vim.notify("no test found fallback to package test", vim.lsp.log_levels.DEBUG) M.test_package(...) return end local tags = M.get_build_tags(args) local test_runner = _GO_NVIM_CFG.go if _GO_NVIM_CFG.test_runner ~= "go" then test_runner = _GO_NVIM_CFG.test_runner require("go.install").install(test_runner) if test_runner == "ginkgo" then ginkgo.test_func(...) end end local relpath = utils.rel_path(true) log(relpath) local cmd_args = {} if run_in_floaterm then table.insert(cmd_args, test_runner) table.insert(cmd_args, "test") end if _GO_NVIM_CFG.verbose_tests then table.insert(cmd_args, "-v") end if tags ~= nil then table.insert(cmd_args, tags) end if next(reminder) then vim.list_extend(cmd_args, reminder) end table.insert(cmd_args, "-run") local sh = vim.o.shell if sh:find("fish") then tests = "'" .. tests .. "'" end table.insert(cmd_args, tests) -- shell script | is a pipe table.insert(cmd_args, relpath) if run_in_floaterm then local term = require("go.term").run cmd_args = richgo(cmd_args) cmd_args = table.concat(cmd_args, " ") log(cmd_args) term({ cmd = cmd_args, autoclose = false }) return cmd_args end if _GO_NVIM_CFG.test_runner == "dlv" then cmd_args = { "dlv", "test", relpath, "--", "-test.run", tests } cmd_args = table.concat(cmd_args, " ") local term = require("go.term").run term({ cmd = cmd_args, autoclose = false }) return cmd_args end vim.cmd([[setl makeprg=]] .. _GO_NVIM_CFG.go .. [[\ test]]) log(cmd_args) local cmdret = require("go.asyncmake").make(unpack(cmd_args)) utils.log("test cmd: ", cmdret, " finished") return cmdret end -- TS based run func -- https://github.com/rentziass/dotfiles/blob/master/vim/.config/nvim/lua/rentziass/lsp/go_tests.lua M.run_file = function() local bufnr = vim.api.nvim_get_current_buf() local tree = vim.treesitter.get_parser(bufnr):parse()[1] local query = vim.treesitter.parse_query("go", require("go.ts.textobjects").query_test_func) local test_names = {} for id, node in query:iter_captures(tree:root(), bufnr, 0, -1) do local name = query.captures[id] -- name of the capture in the query if name == "test_name" then table.insert(test_names, vim.treesitter.query.get_node_text(node, bufnr)) end end vim.schedule(function() vim.lsp.buf.execute_command({ command = "gopls.run_tests", arguments = { { URI = vim.uri_from_bufnr(0), Tests = test_names } }, }) end) end M.get_testfunc = function() local bufnr = get_test_filebufnr() local parser = vim.treesitter.get_parser(bufnr) local tree = parser:parse() tree = tree[1] local query = vim.treesitter.parse_query("go", require("go.ts.go").query_test_func) local test_names = {} for id, node in query:iter_captures(tree:root(), bufnr, 0, -1) do local name = query.captures[id] -- name of the capture in the query if name == "test_name" then table.insert(test_names, vim.treesitter.query.get_node_text(node, bufnr)) end end return test_names end -- GUI to select test? M.select_tests = function() local guihua = utils.load_plugin("guihua.lua", "guihua.gui") local original_select = vim.ui.select if guihua then vim.ui.select = require("guihua.gui").select end vim.defer_fn(function() vim.ui.select = original_select end, 500) local function onselect(item, idx) if not item then return end local uri = vim.uri_from_bufnr(0) log(uri, item, idx) vim.schedule(function() vim.lsp.buf.execute_command({ command = "gopls.run_tests", arguments = { { URI = uri, Tests = { item } } }, }) end) end local test_names = M.get_testfunc() vim.ui.select(test_names, { prompt = "select test to run:", kind = "codelensaction", }, onselect) return test_names end return M