2022-04-16 12:09:15 +00:00
|
|
|
-- run `go test`
|
2021-07-08 16:16:22 +00:00
|
|
|
local M = {}
|
2022-10-20 02:02:45 +00:00
|
|
|
local utils = require('go.utils')
|
2021-11-23 23:13:40 +00:00
|
|
|
local log = utils.log
|
2022-10-20 02:02:45 +00:00
|
|
|
-- local trace = utils.trace
|
|
|
|
local trace = log
|
2021-12-29 14:33:24 +00:00
|
|
|
local empty = utils.empty
|
2022-10-20 02:02:45 +00:00
|
|
|
local ginkgo = require('go.ginkgo')
|
|
|
|
local getopt = require('go.alt_getopt')
|
|
|
|
local install = require('go.install').install
|
2022-06-01 11:29:13 +00:00
|
|
|
local vfn = vim.fn
|
2022-01-16 02:08:16 +00:00
|
|
|
|
|
|
|
local long_opts = {
|
2022-10-20 02:02:45 +00:00
|
|
|
verbose = 'v',
|
|
|
|
compile = 'c',
|
|
|
|
coverage = 'C',
|
|
|
|
count = 'n',
|
|
|
|
tags = 't',
|
2023-01-11 03:53:05 +00:00
|
|
|
fuzz = 'f',
|
2022-10-20 02:02:45 +00:00
|
|
|
bench = 'b',
|
|
|
|
metric = 'm',
|
|
|
|
select = 's',
|
|
|
|
args = 'a',
|
2022-11-22 23:33:51 +00:00
|
|
|
package = 'p',
|
2022-10-20 02:02:45 +00:00
|
|
|
floaterm = 'F',
|
2022-01-16 02:08:16 +00:00
|
|
|
}
|
2022-04-25 04:57:27 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
local sep = require('go.utils').sep()
|
2022-11-22 23:33:51 +00:00
|
|
|
local short_opts = 'a:cC:t:bsFmpn:v'
|
2022-10-20 02:02:45 +00:00
|
|
|
local bench_opts = { '-benchmem', '-cpuprofile', 'profile.out' }
|
2021-07-08 16:16:22 +00:00
|
|
|
|
2021-12-15 08:02:22 +00:00
|
|
|
M.efm = function()
|
2023-01-27 04:06:32 +00:00
|
|
|
local indent = [[%\\%( %\\)]]
|
2021-12-15 08:02:22 +00:00
|
|
|
local efm = [[%-G=== RUN %.%#]]
|
2023-01-27 04:06:32 +00:00
|
|
|
efm = efm .. [[,%-G]] .. indent .. [[%#--- PASS: %.%#]]
|
2021-12-15 08:02:22 +00:00
|
|
|
efm = efm .. [[,%G--- FAIL: %\\%(Example%\\)%\\@=%m (%.%#)]]
|
2023-01-27 04:06:32 +00:00
|
|
|
efm = efm .. [[,%G]] .. indent .. [[%#--- FAIL: %m (%.%#)]]
|
|
|
|
efm = efm .. [[,%A]] .. indent .. [[%\\+%[%^:]%\\+: %f:%l: %m]]
|
2021-12-15 08:02:22 +00:00
|
|
|
efm = efm .. [[,%+Gpanic: test timed out after %.%\\+]]
|
2022-10-20 02:02:45 +00:00
|
|
|
efm = efm .. ',%+Afatal error: %.%# [recovered]'
|
2021-12-15 08:02:22 +00:00
|
|
|
efm = efm .. [[,%+Afatal error: %.%#]]
|
|
|
|
efm = efm .. [[,%+Apanic: %.%#]]
|
2023-01-27 04:06:32 +00:00
|
|
|
--
|
|
|
|
-- -- exit
|
2022-10-20 02:02:45 +00:00
|
|
|
efm = efm .. ',%-Cexit status %[0-9]%\\+'
|
|
|
|
efm = efm .. ',exit status %[0-9]%\\+'
|
2023-01-27 04:06:32 +00:00
|
|
|
-- -- failed lines
|
2022-10-20 02:02:45 +00:00
|
|
|
efm = efm .. ',%-CFAIL%\\t%.%#'
|
|
|
|
efm = efm .. ',FAIL%\\t%.%#'
|
2021-12-15 08:02:22 +00:00
|
|
|
-- compiling error
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
efm = efm .. ',%A%f:%l:%c: %m'
|
|
|
|
efm = efm .. ',%A%f:%l: %m'
|
2023-01-30 10:34:52 +00:00
|
|
|
efm = efm .. ',%f:%l +0x%[0-9A-Fa-f]%\\+' -- pannic with adress
|
|
|
|
efm = efm .. ',%-G%\\t%\\f%\\+:%\\d%\\+ +0x%[0-9A-Fa-f]%\\+' -- test failure, address invalid inside
|
|
|
|
-- multi-line
|
2023-01-27 04:06:32 +00:00
|
|
|
efm = efm .. ',%+G%\\t%m'
|
|
|
|
efm = efm .. ',%-C%.%#' -- ignore rest of unmatched lines
|
2022-10-20 02:02:45 +00:00
|
|
|
efm = efm .. ',%-G%.%#'
|
2023-01-27 04:06:32 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
efm = string.gsub(efm, ' ', [[\ ]])
|
2023-01-25 05:09:13 +00:00
|
|
|
-- log(efm)
|
2021-12-15 08:02:22 +00:00
|
|
|
return efm
|
|
|
|
end
|
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
-- return "-tags=tag1,tag2"
|
2022-08-06 00:49:09 +00:00
|
|
|
M.get_build_tags = function(args, tbl)
|
2021-12-16 07:15:21 +00:00
|
|
|
-- local tags = "-tags"
|
2022-06-18 05:43:07 +00:00
|
|
|
args = args or {}
|
2022-05-30 14:04:52 +00:00
|
|
|
local tags = {}
|
2022-10-20 02:02:45 +00:00
|
|
|
if _GO_NVIM_CFG.build_tags ~= '' then
|
2022-05-30 14:04:52 +00:00
|
|
|
tags = { _GO_NVIM_CFG.build_tags }
|
2021-10-24 23:05:02 +00:00
|
|
|
end
|
|
|
|
|
2022-06-01 11:29:13 +00:00
|
|
|
local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts)
|
2022-11-22 23:33:51 +00:00
|
|
|
log(optarg, reminder)
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['t'] then
|
|
|
|
table.insert(tags, optarg['t'])
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
2022-06-15 04:11:10 +00:00
|
|
|
|
|
|
|
local rt = utils.get_build_tags()
|
|
|
|
if not utils.empty(rt) then
|
|
|
|
vim.list_extend(tags, rt)
|
|
|
|
end
|
|
|
|
|
|
|
|
if #tags > 0 then
|
2022-08-06 00:49:09 +00:00
|
|
|
if tbl then
|
2022-11-22 23:33:51 +00:00
|
|
|
return { '-tags', table.concat(tags, ',') }, reminder, optarg
|
2022-08-06 00:49:09 +00:00
|
|
|
end
|
2022-11-22 23:33:51 +00:00
|
|
|
return '-tags=' .. table.concat(tags, ','), reminder, optarg
|
2022-04-16 12:09:15 +00:00
|
|
|
end
|
2022-11-22 23:33:51 +00:00
|
|
|
return
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
|
|
|
|
2023-01-15 04:46:16 +00:00
|
|
|
function M.get_test_path()
|
2022-10-20 02:02:45 +00:00
|
|
|
local path = vim.fn.expand('%:p:h')
|
|
|
|
local relative_path = vim.fn.fnamemodify(path, ':.')
|
2022-09-06 14:22:13 +00:00
|
|
|
if path == relative_path then
|
|
|
|
return path
|
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
return '.' .. sep .. relative_path
|
2022-09-06 14:22:13 +00:00
|
|
|
end
|
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
local function richgo(cmd)
|
2022-10-20 02:02:45 +00:00
|
|
|
if cmd[1] == 'go' and vfn.executable('richgo') == 1 then
|
|
|
|
cmd[1] = 'richgo'
|
2022-05-30 14:04:52 +00:00
|
|
|
end
|
|
|
|
return cmd
|
|
|
|
end
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-05-29 13:07:39 +00:00
|
|
|
local function get_test_filebufnr()
|
2022-10-20 02:02:45 +00:00
|
|
|
local fn = vfn.expand('%')
|
2022-07-07 11:23:18 +00:00
|
|
|
trace(fn)
|
2022-05-29 13:07:39 +00:00
|
|
|
|
|
|
|
local bufnr = vim.api.nvim_get_current_buf()
|
2022-10-20 02:02:45 +00:00
|
|
|
if not fn:find('test%.go$') then
|
|
|
|
fn = require('go.alternate').alternate()
|
|
|
|
fn = vfn.fnamemodify(fn, ':p') -- expand to full path
|
2022-05-29 13:07:39 +00:00
|
|
|
local uri = vim.uri_from_fname(fn)
|
|
|
|
bufnr = vim.uri_to_bufnr(uri)
|
|
|
|
log(fn, bufnr, uri)
|
2022-06-30 02:41:20 +00:00
|
|
|
if vfn.filereadable(vim.uri_to_fname(uri)) == 0 then
|
|
|
|
-- no test file existed
|
2022-10-20 02:02:45 +00:00
|
|
|
return 0, 'no test file'
|
2022-06-30 02:41:20 +00:00
|
|
|
end
|
2022-05-29 13:07:39 +00:00
|
|
|
if not vim.api.nvim_buf_is_loaded(bufnr) then
|
2022-06-01 11:29:13 +00:00
|
|
|
vfn.bufload(bufnr)
|
2022-05-29 13:07:39 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
return bufnr
|
|
|
|
end
|
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
-- {-c: compile, -v: verbose, -t: tags, -b: bench, -s: select}
|
2022-04-16 12:09:15 +00:00
|
|
|
local function run_test(path, args)
|
2021-11-23 23:13:40 +00:00
|
|
|
log(args)
|
2022-04-16 12:09:15 +00:00
|
|
|
local compile = false
|
|
|
|
local bench = false
|
2022-10-20 02:02:45 +00:00
|
|
|
local extra_args = ''
|
|
|
|
local optarg, oid, reminder = getopt.get_opts(args, short_opts, long_opts)
|
|
|
|
trace(optarg, oid, reminder)
|
|
|
|
if optarg['c'] then
|
2022-06-01 11:29:13 +00:00
|
|
|
path = utils.rel_path(true) -- vfn.expand("%:p:h") can not resolve releative path
|
2022-04-16 12:09:15 +00:00
|
|
|
compile = true
|
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['b'] then
|
2022-04-16 12:09:15 +00:00
|
|
|
bench = true
|
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
|
|
|
|
if optarg['a'] then
|
|
|
|
extra_args = optarg['a']
|
|
|
|
end
|
|
|
|
|
2022-05-10 01:03:15 +00:00
|
|
|
if next(reminder) then
|
|
|
|
path = reminder[1]
|
2023-01-09 10:36:24 +00:00
|
|
|
table.remove(reminder, 1)
|
2022-05-10 01:03:15 +00:00
|
|
|
end
|
2022-01-10 10:26:41 +00:00
|
|
|
local test_runner = _GO_NVIM_CFG.go
|
|
|
|
if _GO_NVIM_CFG.test_runner ~= test_runner then
|
2021-11-23 23:13:40 +00:00
|
|
|
test_runner = _GO_NVIM_CFG.test_runner
|
2022-06-30 16:44:39 +00:00
|
|
|
if not install(test_runner) then
|
2022-10-20 02:02:45 +00:00
|
|
|
test_runner = 'go'
|
2022-06-30 16:44:39 +00:00
|
|
|
end
|
2021-10-24 23:05:02 +00:00
|
|
|
end
|
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
local tags = M.get_build_tags(args)
|
2021-12-16 07:15:21 +00:00
|
|
|
|
2022-04-16 12:09:15 +00:00
|
|
|
log(tags)
|
2022-05-08 10:44:13 +00:00
|
|
|
local cmd = {}
|
2022-05-26 05:07:14 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
local run_in_floaterm = optarg['F'] or _GO_NVIM_CFG.run_in_floaterm
|
2022-05-26 05:07:14 +00:00
|
|
|
if run_in_floaterm then
|
2022-10-20 02:02:45 +00:00
|
|
|
table.insert(cmd, test_runner or 'go')
|
|
|
|
table.insert(cmd, 'test')
|
2022-05-08 10:44:13 +00:00
|
|
|
end
|
|
|
|
|
2021-12-29 14:33:24 +00:00
|
|
|
if not empty(tags) then
|
2022-06-15 04:11:10 +00:00
|
|
|
cmd = vim.list_extend(cmd, { tags })
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
2022-06-05 22:24:55 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['C'] then
|
|
|
|
table.insert(cmd, '-coverprofile=' .. optarg['C'])
|
2022-06-05 22:24:55 +00:00
|
|
|
end
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['n'] then
|
|
|
|
table.insert(cmd, '-count=' .. optarg['n'] or '1')
|
2022-07-07 11:23:18 +00:00
|
|
|
end
|
2022-12-05 15:08:45 +00:00
|
|
|
|
|
|
|
if (optarg['v'] or _GO_NVIM_CFG.verbose_tests) and _GO_NVIM_CFG.test_runner == 'go' then
|
|
|
|
table.insert(cmd, '-v')
|
|
|
|
end
|
2022-04-16 12:09:15 +00:00
|
|
|
if not empty(reminder) then
|
|
|
|
cmd = vim.list_extend(cmd, reminder)
|
2023-01-09 10:36:24 +00:00
|
|
|
log('****', reminder, cmd)
|
2021-12-10 10:13:23 +00:00
|
|
|
end
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-01-16 02:08:16 +00:00
|
|
|
if compile == true then
|
2022-10-20 02:02:45 +00:00
|
|
|
if path ~= '' then
|
|
|
|
table.insert(cmd, '-c')
|
2022-01-16 02:08:16 +00:00
|
|
|
table.insert(cmd, path)
|
|
|
|
end
|
2022-04-16 12:09:15 +00:00
|
|
|
elseif bench == true then
|
2022-10-20 02:02:45 +00:00
|
|
|
if path ~= '' then
|
|
|
|
table.insert(cmd, '-bench=' .. path)
|
2022-04-16 12:09:15 +00:00
|
|
|
else
|
2022-10-20 02:02:45 +00:00
|
|
|
table.insert(cmd, '-bench=.')
|
2022-04-16 12:09:15 +00:00
|
|
|
end
|
|
|
|
vim.list_extend(cmd, bench_opts)
|
2021-12-29 14:33:24 +00:00
|
|
|
else
|
2022-10-20 02:02:45 +00:00
|
|
|
if path ~= '' then
|
2022-01-16 02:08:16 +00:00
|
|
|
table.insert(cmd, path)
|
|
|
|
else
|
2022-10-20 02:02:45 +00:00
|
|
|
local argsstr = '.' .. utils.sep() .. '...'
|
2022-05-30 14:04:52 +00:00
|
|
|
table.insert(cmd, argsstr)
|
2022-01-16 02:08:16 +00:00
|
|
|
end
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
|
|
|
|
if #extra_args > 0 then
|
|
|
|
table.insert(cmd, '-a')
|
|
|
|
table.insert(cmd, extra_args)
|
|
|
|
end
|
2022-04-16 12:09:15 +00:00
|
|
|
utils.log(cmd, args)
|
2022-05-26 05:07:14 +00:00
|
|
|
if run_in_floaterm then
|
2022-10-20 02:02:45 +00:00
|
|
|
install('richgo')
|
|
|
|
local term = require('go.term').run
|
2022-05-30 14:04:52 +00:00
|
|
|
cmd = richgo(cmd)
|
2022-06-07 10:10:38 +00:00
|
|
|
log(cmd)
|
2021-12-10 10:13:23 +00:00
|
|
|
term({ cmd = cmd, autoclose = false })
|
2022-05-24 07:54:52 +00:00
|
|
|
return cmd
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
|
|
|
|
2022-01-10 10:26:41 +00:00
|
|
|
vim.cmd([[setl makeprg=]] .. _GO_NVIM_CFG.go .. [[\ test]])
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
utils.log('test cmd', cmd)
|
|
|
|
return require('go.asyncmake').make(unpack(cmd))
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
M.test = function(...)
|
2021-12-10 10:13:23 +00:00
|
|
|
local args = { ... }
|
2021-11-23 23:13:40 +00:00
|
|
|
log(args)
|
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
local test_opts = {
|
2022-10-20 02:02:45 +00:00
|
|
|
verbose = 'v',
|
|
|
|
compile = 'c',
|
|
|
|
coverage = 'C',
|
|
|
|
tags = 't',
|
|
|
|
bench = 'b',
|
|
|
|
metrics = 'm',
|
|
|
|
floaterm = 'F',
|
|
|
|
nearest = 'n',
|
|
|
|
file = 'f',
|
|
|
|
args = 'a',
|
|
|
|
package = 'p',
|
2022-05-30 14:04:52 +00:00
|
|
|
}
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
local test_short_opts = 'a:vcC:t:bsfmnpF'
|
2022-06-01 11:29:13 +00:00
|
|
|
local optarg, _, reminder = getopt.get_opts(args, test_short_opts, test_opts)
|
2022-05-30 14:04:52 +00:00
|
|
|
|
2022-06-01 11:29:13 +00:00
|
|
|
vfn.setqflist({})
|
2022-05-30 14:04:52 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['n'] then --nearest
|
|
|
|
optarg['n'] = nil
|
2022-05-30 14:04:52 +00:00
|
|
|
local opts = getopt.rebuid_args(optarg, reminder) or {}
|
|
|
|
return M.test_func(unpack(opts))
|
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['f'] then -- currentfile
|
|
|
|
optarg['f'] = nil
|
2022-05-30 14:04:52 +00:00
|
|
|
local opts = getopt.rebuid_args(optarg, reminder) or {}
|
|
|
|
return M.test_file(unpack(opts))
|
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['p'] then -- current package
|
|
|
|
optarg['p'] = nil
|
2022-05-30 14:04:52 +00:00
|
|
|
local opts = getopt.rebuid_args(optarg, reminder) or {}
|
|
|
|
return M.test_package(unpack(opts))
|
|
|
|
end
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['a'] then -- current package
|
|
|
|
log('args', optarg['a'])
|
|
|
|
end
|
2021-12-16 07:15:21 +00:00
|
|
|
local workfolder = utils.work_path()
|
2021-11-23 23:13:40 +00:00
|
|
|
if workfolder == nil then
|
2022-10-20 02:02:45 +00:00
|
|
|
workfolder = '.'
|
2021-10-24 23:05:02 +00:00
|
|
|
end
|
2022-05-30 14:04:52 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
local fpath = workfolder .. utils.sep() .. '...'
|
2022-01-16 02:08:16 +00:00
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
if #reminder > 0 then
|
|
|
|
fpath = reminder[1]
|
|
|
|
end
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
utils.log('fpath :' .. fpath)
|
2022-04-16 12:09:15 +00:00
|
|
|
run_test(fpath, args)
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
M.test_suit = function(...)
|
2021-12-10 10:13:23 +00:00
|
|
|
local args = { ... }
|
2021-11-23 23:13:40 +00:00
|
|
|
log(args)
|
|
|
|
|
2021-12-16 07:15:21 +00:00
|
|
|
local workfolder = utils.work_path()
|
2021-11-23 23:13:40 +00:00
|
|
|
utils.log(args)
|
2022-10-20 02:02:45 +00:00
|
|
|
local fpath = workfolder .. utils.sep() .. '...'
|
2021-12-16 01:34:06 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
utils.log('fpath' .. fpath)
|
2021-11-23 23:13:40 +00:00
|
|
|
|
|
|
|
run_test(fpath, args)
|
|
|
|
end
|
|
|
|
|
|
|
|
M.test_package = function(...)
|
2021-12-10 10:13:23 +00:00
|
|
|
local args = { ... }
|
2021-11-23 23:13:40 +00:00
|
|
|
log(args)
|
2023-01-15 04:46:16 +00:00
|
|
|
local fpath = M.get_test_path() .. sep .. '...'
|
2022-10-20 02:02:45 +00:00
|
|
|
utils.log('fpath: ' .. fpath)
|
2022-05-24 07:54:52 +00:00
|
|
|
return run_test(fpath, args)
|
2021-10-24 23:05:02 +00:00
|
|
|
end
|
|
|
|
|
2022-05-29 13:07:39 +00:00
|
|
|
M.get_test_func_name = function()
|
|
|
|
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
|
|
|
|
row, col = row, col + 1
|
2022-10-20 02:02:45 +00:00
|
|
|
local ns = require('go.ts.go').get_func_method_node_at_pos()
|
2022-05-29 13:07:39 +00:00
|
|
|
if empty(ns) then
|
|
|
|
return nil
|
|
|
|
end
|
2022-06-01 11:29:13 +00:00
|
|
|
if ns == nil or ns.name == nil then
|
|
|
|
return nil
|
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
if not string.find(ns.name, '[T|t]est') then
|
2022-05-29 13:07:39 +00:00
|
|
|
-- 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
|
|
|
|
|
2023-01-11 03:53:05 +00:00
|
|
|
M.get_testcase_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_testcase_node()
|
2021-12-29 14:33:24 +00:00
|
|
|
if empty(ns) then
|
2023-01-11 03:53:05 +00:00
|
|
|
return nil
|
2021-07-08 16:16:22 +00:00
|
|
|
end
|
2023-01-11 03:53:05 +00:00
|
|
|
if ns == nil or ns.name == nil then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
return ns
|
|
|
|
end
|
|
|
|
|
|
|
|
local function run_tests_with_ts_node(args, func_node, tblcase_ns)
|
2022-06-01 11:29:13 +00:00
|
|
|
local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts)
|
2022-08-06 11:11:12 +00:00
|
|
|
local tags = M.get_build_tags(args)
|
2022-10-20 02:02:45 +00:00
|
|
|
utils.log('tags: ', tags)
|
2023-01-11 03:53:05 +00:00
|
|
|
utils.log('parnode' .. vim.inspect(func_node))
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2023-01-11 03:53:05 +00:00
|
|
|
local test_runner = _GO_NVIM_CFG.test_runner or 'go'
|
2021-12-16 07:15:21 +00:00
|
|
|
|
2023-01-11 03:53:05 +00:00
|
|
|
if test_runner ~= 'go' then
|
2022-06-30 16:44:39 +00:00
|
|
|
if not install(test_runner) then
|
2022-10-20 02:02:45 +00:00
|
|
|
test_runner = 'go'
|
2022-06-30 16:44:39 +00:00
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
if test_runner == 'ginkgo' then
|
2023-01-11 03:53:05 +00:00
|
|
|
ginkgo.test_func(unpack(args))
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
local run_flags = '-run'
|
2022-05-29 13:07:39 +00:00
|
|
|
|
2022-05-08 10:44:13 +00:00
|
|
|
local cmd = {}
|
2022-10-20 02:02:45 +00:00
|
|
|
local run_in_floaterm = optarg['F'] or _GO_NVIM_CFG.run_in_floaterm
|
2022-04-16 12:09:15 +00:00
|
|
|
if run_in_floaterm then
|
2022-05-08 10:44:13 +00:00
|
|
|
table.insert(cmd, test_runner)
|
2022-10-20 02:02:45 +00:00
|
|
|
table.insert(cmd, 'test')
|
2022-05-08 10:44:13 +00:00
|
|
|
end
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['s'] then
|
2022-05-24 09:17:26 +00:00
|
|
|
return M.select_tests()
|
|
|
|
end
|
2022-12-05 15:08:45 +00:00
|
|
|
if (optarg['v'] or _GO_NVIM_CFG.verbose_tests) and _GO_NVIM_CFG.test_runner == 'go' then
|
2022-10-20 02:02:45 +00:00
|
|
|
table.insert(cmd, '-v')
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
2022-05-08 10:44:13 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if tags and tags ~= '' then
|
2022-05-30 14:04:52 +00:00
|
|
|
table.insert(cmd, tags)
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
2023-01-11 03:53:05 +00:00
|
|
|
if func_node == nil or func_node.name == nil then
|
2022-06-01 11:29:13 +00:00
|
|
|
return
|
|
|
|
end
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['n'] then
|
2023-01-11 03:53:05 +00:00
|
|
|
table.insert(cmd, '-count=' .. (optarg['n'] or '1'))
|
|
|
|
end
|
|
|
|
|
2023-01-12 04:25:47 +00:00
|
|
|
local tbl_name = ''
|
2023-01-11 03:53:05 +00:00
|
|
|
if tblcase_ns and tblcase_ns.name then
|
|
|
|
tbl_name = tblcase_ns.name:gsub('/', '//')
|
|
|
|
tbl_name = tbl_name:gsub('%(', '\\(')
|
|
|
|
tbl_name = tbl_name:gsub('%)', '\\)')
|
|
|
|
tbl_name = '/' .. tbl_name
|
2022-07-07 11:23:18 +00:00
|
|
|
end
|
2023-01-11 03:53:05 +00:00
|
|
|
|
|
|
|
if func_node.name:find('Bench') then
|
|
|
|
local bench = '-bench=' .. func_node.name .. tbl_name
|
2021-12-16 07:15:21 +00:00
|
|
|
table.insert(cmd, bench)
|
2022-04-16 12:09:15 +00:00
|
|
|
vim.list_extend(cmd, bench_opts)
|
2023-01-11 03:53:05 +00:00
|
|
|
elseif func_node.name:find('Fuzz') then
|
|
|
|
table.insert(cmd, '-fuzz')
|
|
|
|
table.insert(cmd, func_node.name)
|
2022-04-16 12:09:15 +00:00
|
|
|
else
|
2022-05-30 14:04:52 +00:00
|
|
|
table.insert(cmd, run_flags)
|
2023-01-11 03:53:05 +00:00
|
|
|
table.insert(cmd, [['^]] .. func_node.name .. [[$']] .. tbl_name)
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
2022-05-29 13:07:39 +00:00
|
|
|
|
2023-01-15 04:46:16 +00:00
|
|
|
local fpath = M.get_test_path()
|
2021-12-16 07:15:21 +00:00
|
|
|
table.insert(cmd, fpath)
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-10-20 02:09:38 +00:00
|
|
|
if optarg['a'] then
|
|
|
|
table.insert(cmd, '-a')
|
|
|
|
table.insert(cmd, optarg['a'])
|
|
|
|
end
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
if test_runner == 'dlv' then
|
2023-01-11 03:53:05 +00:00
|
|
|
local runflag = string.format("-test.run='^%s$'%s", func_node.name, tbl_name)
|
2022-10-20 02:02:45 +00:00
|
|
|
if tags and #tags > 0 then
|
2023-01-12 04:25:47 +00:00
|
|
|
cmd = { 'dlv', 'test', fpath, '--build-flags', tags, '--', runflag }
|
2022-08-19 22:33:03 +00:00
|
|
|
else
|
2023-01-11 03:53:05 +00:00
|
|
|
cmd = { 'dlv', 'test', fpath, '--', runflag }
|
2022-08-19 22:33:03 +00:00
|
|
|
end
|
2023-01-11 03:53:05 +00:00
|
|
|
log(cmd)
|
2022-10-20 02:02:45 +00:00
|
|
|
local term = require('go.term').run
|
2022-05-29 13:07:39 +00:00
|
|
|
term({ cmd = cmd, autoclose = false })
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2022-04-16 12:09:15 +00:00
|
|
|
if run_in_floaterm then
|
2021-11-23 23:13:40 +00:00
|
|
|
utils.log(cmd)
|
2022-10-20 02:02:45 +00:00
|
|
|
install('richgo')
|
|
|
|
local term = require('go.term').run
|
2022-05-30 14:04:52 +00:00
|
|
|
cmd = richgo(cmd)
|
2021-12-10 10:13:23 +00:00
|
|
|
term({ cmd = cmd, autoclose = false })
|
2021-11-23 23:13:40 +00:00
|
|
|
return
|
|
|
|
end
|
2022-05-24 09:17:26 +00:00
|
|
|
vim.list_extend(cmd, reminder)
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2021-12-16 07:15:21 +00:00
|
|
|
vim.cmd([[setl makeprg=]] .. test_runner .. [[\ test]])
|
2021-12-15 08:02:22 +00:00
|
|
|
-- set_efm()
|
2022-10-20 02:02:45 +00:00
|
|
|
utils.log('test cmd', cmd)
|
2022-05-29 13:07:39 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
return require('go.asyncmake').make(unpack(cmd))
|
2023-01-11 03:53:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
--options {s:select, F: floaterm}
|
|
|
|
M.test_func = function(...)
|
2023-01-17 22:20:11 +00:00
|
|
|
local args = { ... } or {}
|
2023-01-11 03:53:05 +00:00
|
|
|
log(args)
|
|
|
|
|
|
|
|
local ns = M.get_test_func_name()
|
|
|
|
if empty(ns) then
|
|
|
|
return M.select_tests()
|
|
|
|
end
|
2023-01-12 04:25:47 +00:00
|
|
|
|
|
|
|
local parser_path = vim.api.nvim_get_runtime_file('parser' .. sep .. 'go.so', false)[1]
|
|
|
|
if not parser_path then
|
|
|
|
-- require('nvim-treesitter.install').commands.TSInstallSync['run!']('go')
|
|
|
|
vim.notify('go treesitter parser not found, please Run `:TSInstallSync go`', vim.log.levels.WARN)
|
|
|
|
end
|
2023-01-11 03:53:05 +00:00
|
|
|
return run_tests_with_ts_node(args, ns)
|
|
|
|
end
|
|
|
|
|
|
|
|
--options {s:select, F: floaterm}
|
|
|
|
M.test_tblcase = function(...)
|
|
|
|
local args = { ... }
|
|
|
|
log(args)
|
|
|
|
|
|
|
|
local ns = M.get_test_func_name()
|
|
|
|
if empty(ns) then
|
|
|
|
vim.notify('put cursor on test case name string')
|
|
|
|
end
|
|
|
|
|
|
|
|
local tblcase_ns = M.get_testcase_name()
|
|
|
|
if empty(tblcase_ns) then
|
|
|
|
vim.notify('put cursor on test case name string')
|
|
|
|
end
|
|
|
|
return run_tests_with_ts_node(args, ns, tblcase_ns)
|
2021-07-08 16:16:22 +00:00
|
|
|
end
|
|
|
|
|
2023-01-25 09:55:51 +00:00
|
|
|
M.get_test_cases = function()
|
2022-10-20 02:02:45 +00:00
|
|
|
local fpath = '.' .. sep .. vfn.fnamemodify(vfn.expand('%:p'), ':.')
|
2023-01-22 23:17:12 +00:00
|
|
|
local is_test = fpath:find('_test%.go$')
|
|
|
|
if not is_test then
|
|
|
|
fpath = '.' .. sep .. vfn.fnamemodify(vfn.expand('%:p'), ':.:r') .. '_test.go'
|
|
|
|
end
|
2021-12-16 07:15:21 +00:00
|
|
|
-- utils.log(args)
|
2023-01-30 10:34:52 +00:00
|
|
|
local tests = M.get_testfunc()
|
|
|
|
if not tests then
|
|
|
|
local cmd = [[cat ]] .. fpath .. [[| sed -n 's/func\s\+\(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
|
|
|
|
return
|
|
|
|
end
|
2022-04-16 12:09:15 +00:00
|
|
|
|
2023-01-30 10:34:52 +00:00
|
|
|
local tests = vfn.systemlist(cmd)
|
|
|
|
if vim.v.shell_error ~= 0 then
|
|
|
|
utils.warn('go test failed' .. vim.inspect(tests))
|
|
|
|
return
|
|
|
|
end
|
|
|
|
return tests[1]
|
2022-06-28 04:36:30 +00:00
|
|
|
end
|
2023-01-30 10:34:52 +00:00
|
|
|
tests = vim.fn.join(tests, '\\|')
|
|
|
|
utils.log(tests)
|
2023-01-25 09:55:51 +00:00
|
|
|
return tests
|
|
|
|
end
|
|
|
|
|
|
|
|
M.test_file = function(...)
|
|
|
|
local args = { ... }
|
|
|
|
log(args)
|
|
|
|
|
|
|
|
-- require sed
|
|
|
|
local tests = M.get_test_cases()
|
|
|
|
if not tests then
|
|
|
|
vim.notify('no test found fallback to package test', vim.lsp.log_levels.DEBUG)
|
|
|
|
return M.test_package(...)
|
|
|
|
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
|
|
|
|
|
2022-06-01 11:29:13 +00:00
|
|
|
if vfn.empty(tests) == 1 then
|
2022-10-20 02:02:45 +00:00
|
|
|
vim.notify('no test found fallback to package test', vim.lsp.log_levels.DEBUG)
|
2022-01-11 00:57:46 +00:00
|
|
|
M.test_package(...)
|
2021-12-15 08:02:22 +00:00
|
|
|
return
|
2021-11-24 09:05:02 +00:00
|
|
|
end
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
local tags = M.get_build_tags(args)
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-01-10 10:26:41 +00:00
|
|
|
local test_runner = _GO_NVIM_CFG.go
|
2022-10-20 02:02:45 +00:00
|
|
|
if _GO_NVIM_CFG.test_runner ~= 'go' then
|
2021-11-23 23:13:40 +00:00
|
|
|
test_runner = _GO_NVIM_CFG.test_runner
|
2022-06-30 16:44:39 +00:00
|
|
|
if not install(test_runner) then
|
2022-10-20 02:02:45 +00:00
|
|
|
test_runner = 'go'
|
2022-06-30 16:44:39 +00:00
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
if test_runner == 'ginkgo' then
|
2022-05-29 13:07:39 +00:00
|
|
|
ginkgo.test_func(...)
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-05-26 05:07:14 +00:00
|
|
|
local relpath = utils.rel_path(true)
|
|
|
|
log(relpath)
|
2021-11-23 23:13:40 +00:00
|
|
|
|
2022-05-08 10:44:13 +00:00
|
|
|
local cmd_args = {}
|
2022-04-16 12:09:15 +00:00
|
|
|
if run_in_floaterm then
|
2022-05-08 10:44:13 +00:00
|
|
|
table.insert(cmd_args, test_runner)
|
2022-10-20 02:02:45 +00:00
|
|
|
table.insert(cmd_args, 'test')
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
2022-05-08 10:44:13 +00:00
|
|
|
|
2022-12-05 15:08:45 +00:00
|
|
|
if (optarg['v'] or _GO_NVIM_CFG.verbose_tests) and _GO_NVIM_CFG.test_runner == 'go' then
|
2022-10-20 02:02:45 +00:00
|
|
|
table.insert(cmd_args, '-v')
|
2022-05-08 10:44:13 +00:00
|
|
|
end
|
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
if tags ~= nil then
|
|
|
|
table.insert(cmd_args, tags)
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
|
|
|
|
2022-05-30 14:04:52 +00:00
|
|
|
if next(reminder) then
|
|
|
|
vim.list_extend(cmd_args, reminder)
|
2021-12-16 07:15:21 +00:00
|
|
|
end
|
2022-10-20 02:02:45 +00:00
|
|
|
if optarg['n'] then
|
|
|
|
table.insert(cmd_args, '-count=' .. optarg['n'] or '1')
|
2022-07-07 11:23:18 +00:00
|
|
|
end
|
2023-01-11 03:53:05 +00:00
|
|
|
table.insert(cmd_args, '-run')
|
|
|
|
|
2022-06-16 01:38:37 +00:00
|
|
|
local sh = vim.o.shell
|
2022-10-20 02:02:45 +00:00
|
|
|
if sh:find('fish') then
|
2022-06-29 05:01:17 +00:00
|
|
|
tests = "'" .. tests .. "'"
|
2022-06-16 01:38:37 +00:00
|
|
|
end
|
2022-06-07 10:10:38 +00:00
|
|
|
table.insert(cmd_args, tests) -- shell script | is a pipe
|
2021-12-16 07:15:21 +00:00
|
|
|
table.insert(cmd_args, relpath)
|
2021-12-16 01:34:06 +00:00
|
|
|
|
2022-04-16 12:09:15 +00:00
|
|
|
if run_in_floaterm then
|
2022-10-20 02:02:45 +00:00
|
|
|
install('richgo')
|
|
|
|
local term = require('go.term').run
|
2022-05-30 14:04:52 +00:00
|
|
|
cmd_args = richgo(cmd_args)
|
2022-10-20 02:02:45 +00:00
|
|
|
cmd_args = table.concat(cmd_args, ' ')
|
2022-06-07 10:10:38 +00:00
|
|
|
log(cmd_args)
|
2021-12-16 07:15:21 +00:00
|
|
|
term({ cmd = cmd_args, autoclose = false })
|
2022-06-07 10:10:38 +00:00
|
|
|
return cmd_args
|
2021-11-23 23:13:40 +00:00
|
|
|
end
|
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
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
|
2022-05-29 13:07:39 +00:00
|
|
|
term({ cmd = cmd_args, autoclose = false })
|
2022-06-07 10:10:38 +00:00
|
|
|
return cmd_args
|
2022-05-29 13:07:39 +00:00
|
|
|
end
|
|
|
|
|
2022-01-10 10:26:41 +00:00
|
|
|
vim.cmd([[setl makeprg=]] .. _GO_NVIM_CFG.go .. [[\ test]])
|
2022-05-29 13:07:39 +00:00
|
|
|
log(cmd_args)
|
2022-05-24 07:54:52 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
local cmdret = require('go.asyncmake').make(unpack(cmd_args))
|
2022-05-24 07:54:52 +00:00
|
|
|
|
2022-10-20 02:02:45 +00:00
|
|
|
utils.log('test cmd: ', cmdret, ' finished')
|
2022-05-24 07:54:52 +00:00
|
|
|
return cmdret
|
2021-08-31 11:19:14 +00:00
|
|
|
end
|
|
|
|
|
2022-04-18 12:31:17 +00:00
|
|
|
-- 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]
|
2022-10-20 02:02:45 +00:00
|
|
|
local query = vim.treesitter.parse_query('go', require('go.ts.textobjects').query_test_func)
|
2022-04-18 12:31:17 +00:00
|
|
|
|
|
|
|
local test_names = {}
|
2022-06-15 04:11:10 +00:00
|
|
|
for id, node in query:iter_captures(tree:root(), bufnr, 0, -1) do
|
2022-04-18 12:31:17 +00:00
|
|
|
local name = query.captures[id] -- name of the capture in the query
|
2022-10-20 02:02:45 +00:00
|
|
|
if name == 'test_name' then
|
2022-04-18 14:22:08 +00:00
|
|
|
table.insert(test_names, vim.treesitter.query.get_node_text(node, bufnr))
|
2022-04-18 12:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
vim.schedule(function()
|
|
|
|
vim.lsp.buf.execute_command({
|
2022-10-20 02:02:45 +00:00
|
|
|
command = 'gopls.run_tests',
|
2022-04-18 12:31:17 +00:00
|
|
|
arguments = { { URI = vim.uri_from_bufnr(0), Tests = test_names } },
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2022-05-24 11:43:33 +00:00
|
|
|
M.get_testfunc = function()
|
2022-05-29 13:07:39 +00:00
|
|
|
local bufnr = get_test_filebufnr()
|
2023-02-01 00:55:07 +00:00
|
|
|
|
|
|
|
-- Note: the buffer may not be loaded yet
|
|
|
|
local ok, parser = pcall(vim.treesitter.get_parser, bufnr)
|
|
|
|
if not ok or not parser then return end
|
2022-05-29 13:07:39 +00:00
|
|
|
local tree = parser:parse()
|
|
|
|
tree = tree[1]
|
2022-10-20 02:02:45 +00:00
|
|
|
local query = vim.treesitter.parse_query('go', require('go.ts.go').query_test_func)
|
2022-05-24 11:43:33 +00:00
|
|
|
|
2022-04-18 12:31:17 +00:00
|
|
|
local test_names = {}
|
2022-06-01 11:29:13 +00:00
|
|
|
for id, node in query:iter_captures(tree:root(), bufnr, 0, -1) do
|
2022-04-18 12:31:17 +00:00
|
|
|
local name = query.captures[id] -- name of the capture in the query
|
2022-10-20 02:02:45 +00:00
|
|
|
if name == 'test_name' then
|
2022-04-18 14:22:08 +00:00
|
|
|
table.insert(test_names, vim.treesitter.query.get_node_text(node, bufnr))
|
2022-04-18 12:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-05-24 11:43:33 +00:00
|
|
|
return test_names
|
|
|
|
end
|
|
|
|
|
|
|
|
-- GUI to select test?
|
|
|
|
M.select_tests = function()
|
2022-10-20 02:02:45 +00:00
|
|
|
local guihua = utils.load_plugin('guihua.lua', 'guihua.gui')
|
2022-04-18 12:31:17 +00:00
|
|
|
local original_select = vim.ui.select
|
|
|
|
|
|
|
|
if guihua then
|
2022-10-20 02:02:45 +00:00
|
|
|
vim.ui.select = require('guihua.gui').select
|
2022-04-18 12:31:17 +00:00
|
|
|
end
|
|
|
|
|
2022-05-24 09:17:26 +00:00
|
|
|
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({
|
2022-10-20 02:02:45 +00:00
|
|
|
command = 'gopls.run_tests',
|
2022-05-24 09:17:26 +00:00
|
|
|
arguments = { { URI = uri, Tests = { item } } },
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
end
|
2022-05-29 13:07:39 +00:00
|
|
|
local test_names = M.get_testfunc()
|
2022-05-24 09:17:26 +00:00
|
|
|
vim.ui.select(test_names, {
|
2022-10-20 02:02:45 +00:00
|
|
|
prompt = 'select test to run:',
|
|
|
|
kind = 'codelensaction',
|
2022-05-24 09:17:26 +00:00
|
|
|
}, onselect)
|
|
|
|
return test_names
|
2022-04-18 12:31:17 +00:00
|
|
|
end
|
|
|
|
|
2021-07-08 16:16:22 +00:00
|
|
|
return M
|