diff --git a/lua/neotest-go/init.lua b/lua/neotest-go/init.lua index 00b6e13..5f20adb 100644 --- a/lua/neotest-go/init.lua +++ b/lua/neotest-go/init.lua @@ -185,46 +185,69 @@ function adapter.prepare_results(tree, lines, go_root, go_module) local results = {} local no_results = vim.tbl_isempty(tests) local empty_result_fname - local file_id empty_result_fname = async.fn.tempname() fn.writefile(log, empty_result_fname) for _, node in tree:iter_nodes() do - local value = node:data() + local node_data = node:data() if no_results then - results[value.id] = { + results[node_data.id] = { status = test_statuses.fail, output = empty_result_fname, } break end - if value.type == "file" then - results[value.id] = { - status = test_statuses.pass, - output = empty_result_fname, - } - file_id = value.id - else - local normalized_id = utils.normalize_id(value.id, go_root, go_module) - local test_result = tests[normalized_id] - -- file level node - if test_result then - local fname = async.fn.tempname() - fn.writefile(test_result.output, fname) - results[value.id] = { - status = test_result.status, - short = table.concat(test_result.output, ""), - output = fname, - } - local errors = utils.get_errors_from_test(test_result, utils.get_filename_from_id(value.id)) - if errors then - results[value.id].errors = errors + + if node_data.type ~= "file" then + local file_path = node_data.path + local file_id = utils.get_filename_from_id(node_data.id) + local normalized_node_test_id = utils.normalize_id(node_data.id, go_root, go_module) + local node_test_result = tests[normalized_node_test_id] + + if node_test_result then + if not results[file_path] then + results[file_path] = { + status = test_statuses.pass, + output = empty_result_fname, + } end - if test_result.status == test_statuses.fail and file_id then - results[file_id].status = test_statuses.fail + + local node_fname = async.fn.tempname() + fn.writefile(node_test_result.output, node_fname) + results[node_data.id] = { + status = node_test_result.status, + short = table.concat(node_test_result.output, ""), + output = node_fname, + errors = {}, + } + + for _, test_result in pairs(tests) do + if test_result.parenttestname == normalized_node_test_id then + local test_id, _ = utils.normalize_test_name(file_path, test_result.test) + local test_fname = async.fn.tempname() + fn.writefile(test_result.output, test_fname) + results[test_id] = { + status = test_result.status, + short = table.concat(test_result.output, ""), + output = test_fname, + errors = {}, + } + + local errors = utils.get_errors_from_test(test_result, file_id) + if errors then + for _, error in ipairs(errors) do + table.insert(results[node_data.id].errors, error) + end + end + + if test_result.status == test_statuses.fail then + results[file_path].status = test_statuses.fail + end + end end end end end + return results end diff --git a/lua/neotest-go/output.lua b/lua/neotest-go/output.lua index f23ebd9..01543a2 100644 --- a/lua/neotest-go/output.lua +++ b/lua/neotest-go/output.lua @@ -14,7 +14,16 @@ local function sanitize_output(output) if not output then return nil end - output = output:gsub(patterns.testfile, ""):gsub(patterns.testlog, "") + + for _, pattern in ipairs({ + patterns.testfile, + patterns.testify_assert_file, + patterns.testify_callstack_testfile, + patterns.testlog, + }) do + output = output:gsub(pattern, "") + end + return output end @@ -52,6 +61,8 @@ function M.marshal_gotest_output(lines) output = {}, progress = {}, file_output = {}, + parenttestname = parenttestname, + test = test, } end @@ -69,7 +80,7 @@ function M.marshal_gotest_output(lines) local sanitized_output = sanitize_output(parsed.Output) if sanitized_output and not sanitized_output:match("^%s*$") then tests[testname].file_output[testfile][linenumber] = { - sanitize_output(parsed.Output), + sanitized_output, } else tests[testname].file_output[testfile][linenumber] = {} @@ -78,6 +89,14 @@ function M.marshal_gotest_output(lines) -- if we are in the context of a file, collect the logged data if testfile and linenumber and utils.is_test_logoutput(parsed.Output) then + if not tests[testname].file_output[testfile] then + tests[testname].file_output[testfile] = { [linenumber] = {} } + end + + if not tests[testname].file_output[testfile][linenumber] then + tests[testname].file_output[testfile] = { [linenumber] = {} } + end + table.insert( tests[testname].file_output[testfile][linenumber], sanitize_output(parsed.Output) diff --git a/lua/neotest-go/patterns.lua b/lua/neotest-go/patterns.lua index 5987073..c29cd5c 100644 --- a/lua/neotest-go/patterns.lua +++ b/lua/neotest-go/patterns.lua @@ -1,7 +1,10 @@ local patterns = { testfile = "^%s%s%s%s(.*_test.go):(%d+): ", - testlog = "^%s%s%s%s%s%s%s%s", - error = { "error" }, + testify_assert_error_trace = "%S*Error Trace:%s*.*/(.*_test%.go):(%d+)", + testify_assert_error_file = "%S*.*/(.*_test%.go):(%d+)", + testifY_callstack_testfile = ".*at:%s*%[(.-_test%.go):(%d+)%]", + testlog = "^%s*(%S.*)", + error = { "error", "fail" }, } return patterns diff --git a/lua/neotest-go/utils.lua b/lua/neotest-go/utils.lua index 23beae8..6c5ef27 100644 --- a/lua/neotest-go/utils.lua +++ b/lua/neotest-go/utils.lua @@ -67,7 +67,7 @@ end ---@param line string ---@return boolean function utils.is_test_logoutput(line) - return line and line:match(patterns.testlog) ~= nil + return line and line:match(patterns.testlog) end ---@return string @@ -133,8 +133,35 @@ end ---@return string?, number? function utils.get_test_file_info(line) if line then - local file, linenumber = string.match(line, patterns.testfile) - return file, tonumber(linenumber) + for _, pattern in ipairs({ + patterns.testfile, + patterns.testify_assert_error_file, + patterns.testify_assert_error_trace, + }) do + local file, linenumber = string.match(line, pattern) + if file and linenumber then + return file, tonumber(linenumber) + end + end + + -- testify will sometimes return errors as a callstack with the format + -- at: [/some-package/file.go:104 /my-package/my_test.go:933]\n + local last_file = nil + local last_linenumber = nil + + for callstack in string.gmatch(line, "%[(.-)%]") do + for frame in string.gmatch(callstack, "%S+") do + local file, linenumber = string.match(frame, "([^/]*._test%.go):(%d+)") + if file and linenumber then + last_file = file + last_linenumber = linenumber + end + end + end + + if last_file and last_linenumber then + return last_file, tonumber(last_linenumber) + end end return nil, nil end diff --git a/lua/spec/neotest-go/init_spec.lua b/lua/spec/neotest-go/init_spec.lua index 10cf3b2..61dc4db 100644 --- a/lua/spec/neotest-go/init_spec.lua +++ b/lua/spec/neotest-go/init_spec.lua @@ -116,6 +116,7 @@ describe("discover_positions", function() }, }, } + assert.are.same(positions, expected_positions) end) async.it("discovers positions in unit tests in cases_test.go", function() @@ -126,7 +127,7 @@ describe("discover_positions", function() id = vim.loop.cwd() .. "/neotest_go/cases_test.go", name = "cases_test.go", path = vim.loop.cwd() .. "/neotest_go/cases_test.go", - range = { 0, 0, 49, 0 }, + range = { 0, 0, 69, 0 }, type = "file", }, { @@ -183,6 +184,42 @@ describe("discover_positions", function() }, }, }, + { + { + id = vim.loop.cwd() .. "/neotest_go/cases_test.go::TestOuter", + name = "TestOuter", + path = vim.loop.cwd() .. "/neotest_go/cases_test.go", + range = { 50, 0, 52, 1 }, + type = "test", + }, + }, + { + { + id = vim.loop.cwd() .. "/neotest_go/cases_test.go::testAddValues", + name = '"testAddValues"', + path = vim.loop.cwd() .. "/neotest_go/cases_test.go", + range = { 55, 1, 63, 3 }, + type = "test", + }, + { + { + id = vim.loop.cwd() .. '/neotest_go/cases_test.go::"testAddValues"::testAdd2', + name = '"testAdd2"', + path = vim.loop.cwd() .. "/neotest_go/cases_test.go", + range = { 56, 2, 58, 4 }, + type = "test", + }, + }, + { + { + id = vim.loop.cwd() .. '/neotest_go/cases_test.go::"testAddValues"::testAdd5', + name = '"testAdd5"', + path = vim.loop.cwd() .. "/neotest_go/cases_test.go", + range = { 60, 2, 62, 4 }, + type = "test", + }, + }, + }, } assert.are.same(positions, expected_positions) @@ -202,6 +239,11 @@ describe("prepare_results", function() test_file .. "::TestAdd", test_file .. "::TestAdd::test_one", test_file .. "::TestAdd::test_two", + test_file .. "::TestAdd::string", + test_file .. "::TestOuter", + test_file .. "::TestOuter::testAddValues", + test_file .. "::TestOuter::testAddValues::testAdd2", + test_file .. "::TestOuter::testAddValues::testAdd5", test_file .. "::TestSubtract", test_file .. "::TestSubtract::test_one", test_file .. "::TestSubtract::test_two", @@ -228,6 +270,7 @@ describe("prepare_results", function() for s in result:gmatch("[^\r\n]+") do table.insert(lines, s) end + local processed_results = plugin.prepare_results(positions, lines, tests_folder, "neotest_go") for _, v in pairs(expected_keys) do diff --git a/neotest_go/cases_test.go b/neotest_go/cases_test.go index 7c39201..27ac88b 100644 --- a/neotest_go/cases_test.go +++ b/neotest_go/cases_test.go @@ -47,3 +47,23 @@ func TestAdd(t *testing.T) { assert.Equal(t, 3, add(1, 2)) }) } + +func TestOuter(t *testing.T) { + testInnerHelper(t) +} + +func testInnerHelper(t *testing.T) { + t.Run("testAddValues", func(t *testing.T) { + t.Run("testAdd2", func(t *testing.T) { + testAddValues(t, 1, 2) + }) + + t.Run("testAdd5", func(t *testing.T) { + testAddValues(t, 0, 5) + }) + }) +} + +func testAddValues(t *testing.T, want, num int) { + assert.Equal(t, want, addOne(num)) +}