Skip to content

Commit 6c8c9f6

Browse files
committed
fix: output parsing
These changes now allow for correct parsing of the Gradle test output.
1 parent 5e4eab9 commit 6c8c9f6

File tree

2 files changed

+97
-127
lines changed

2 files changed

+97
-127
lines changed

lua/neotest-kotlin/init.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function M.Adapter.build_spec(args)
9595
stream = function()
9696
return function()
9797
local new_results = stream_data()
98-
local success, parsed_result = pcall(output_parser.lines_to_results, new_results, pos.path, specPackage)
98+
local success, parsed_result = pcall(output_parser.parse_lines, new_results, pos.path, specPackage)
9999
if not success then
100100
print("An error ocurred while attempting to stream data to result: " ..
101101
vim.inspect(err) .. " new_results: " .. vim.inspect(new_results))
Lines changed: 96 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,110 @@
11
local M = {}
22

3-
-- the gradle prefix is this long >>> 2024-05-19T22:15:04.339-0400 [DEBUG] [TestEventLogger]
4-
local PREFIX_OFFSET = 54
5-
6-
-- This will get a result type ( NONE, PASSED, SKIPPED, FAILED ) from a gradle output line.
7-
M.get_result_type = function(line, pkg)
8-
-- This will quickly skip non-TestEventLogger lines most of the time...
9-
if string.sub(line, PREFIX_OFFSET, PREFIX_OFFSET) ~= "]" then
10-
return nil
11-
end
12-
13-
-- Test to see if the line starting after the prefix is the package name.
14-
if not string.find(line, pkg, PREFIX_OFFSET + 1) then
15-
return nil
16-
end
17-
18-
if string.find(line, "PASSED", -6) then
19-
return "passed"
20-
end
21-
22-
if string.find(line, "SKIPPED", -7) then
23-
return "skipped"
24-
end
25-
26-
if string.find(line, "FAILED", -6) then
27-
return "failed"
28-
end
29-
30-
return nil
3+
---Gets the result of a Gradle test output line
4+
---@param line string
5+
---@return string status passed, skipped, failed, none
6+
M.parse_status = function(line)
7+
local result = "none"
8+
9+
if vim.endswith(line, "PASSED") then
10+
result = "passed"
11+
elseif vim.endswith(line, "SKIPPED") then
12+
result = "skipped"
13+
elseif vim.endswith(line, "FAILED") then
14+
result = "failed"
15+
end
16+
17+
return result
3118
end
3219

33-
-- This will turn a gradle output line into an id that can be looked up by neotest
34-
-- Example Input: '2024-05-19T22:15:04.339-0400 [DEBUG] [TestEventLogger] com.codymikol.state.neotest.NeotestKotestSpec > a namespace > com.codymikol.state.neotest.NeotestKotestSpec.should handle passed assertions PASSED'
35-
-- Example Output: '/home/cody/dev/src/git-down/src/test/kotlin/com/codymikol/state/neotest.kt::"NeotestKotestSpec"::"a namespace"::"should handle passed assertions"'
36-
M.make_result_id = function(line, path)
37-
--
38-
-- get the line starting after the PREFIX_OFFSET
39-
local line_without_prefix = string.sub(line, PREFIX_OFFSET + 2)
40-
41-
local id = path
42-
43-
local parts = vim.split(line_without_prefix, " > ", { trimempty = true })
44-
45-
-- Cleaning the fully qualified class name
46-
47-
local fullyQualifiedClassName = parts[1]
48-
49-
local fullyQualifiedClassNameParts = vim.split(fullyQualifiedClassName, "%.")
50-
51-
local className = fullyQualifiedClassNameParts[#fullyQualifiedClassNameParts]
52-
53-
parts[1] = className
54-
55-
table.remove(parts, 1)
56-
57-
-- cleaning the it description that is for some reason prepended with the fully qualified class name...
58-
59-
local itDescription = parts[#parts]
60-
61-
local cleanedItDescription = string.sub(itDescription, fullyQualifiedClassName:len() + 2)
62-
63-
parts[#parts] = cleanedItDescription
64-
65-
for _, value in ipairs(parts) do
66-
id = id .. "::" .. '"' .. value .. '"'
67-
end
68-
69-
-- I'm sure there is a better way to do this, but I'm sleepy...
70-
71-
if string.find(id, ' PASSED"', -8) then
72-
return string.sub(id, 1, -9) .. '"'
73-
end
74-
75-
if string.find(id, ' SKIPPED"', -9) then
76-
return string.sub(id, 1, -10) .. '"'
77-
end
78-
79-
if string.find(id, ' FAILED"', -8) then
80-
return string.sub(id, 1, -9) .. '"'
81-
end
82-
83-
return result
20+
-- org.example.KotestDescribeSpec > a namespace > should handle failed assertions FAILED
21+
-- '/home/nick/GitHub/neotest-kotlin/lua/tests/example_project/app/src/test/kotlin/org/example/KotestDescribeExample.kt::"a namespace"::"a nested namespace"::"should handle failed assertions"'
22+
---Parses the Neotest id from a Gradle test line output
23+
---@param line string
24+
---@param path string
25+
---@param package string
26+
---@return string? neotest_id
27+
M.parse_test_id = function(line, path, package)
28+
if not M.is_valid_gradle_test_line(line, package) then
29+
return nil
30+
end
31+
32+
local split = vim.split(line, ">", { trimempty = true })
33+
-- Must have at least "fqn > test"
34+
if #split < 2 then
35+
return nil
36+
end
37+
38+
local names = { unpack(split, 2) }
39+
40+
local result = path
41+
for i, segment in ipairs(names) do
42+
segment = vim.trim(segment)
43+
if (i + 1) == #split then
44+
segment = segment:match("(.+) [PASSED|FAILED|SKIPPED]")
45+
end
46+
47+
if vim.startswith(segment, package .. ".") then
48+
segment = segment:sub(#package + 2)
49+
end
50+
51+
result = result .. '::"' .. segment .. '"'
52+
end
53+
54+
return result
8455
end
8556

86-
local function escape_magic_chars(str)
87-
return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
88-
end
57+
---Whether the line is a valid gradle test line
58+
---@param line string
59+
---@return boolean
60+
function M.is_valid_gradle_test_line(line, package)
61+
if not vim.startswith(line, package) then
62+
return false
63+
end
8964

90-
M.get_result_short = function(line)
91-
local parts = vim.split(line, ">", { trim = true })
92-
local short_with_status = parts[#parts]
93-
local short_with_status_parts = vim.split(short_with_status, " ")
94-
table.remove(short_with_status_parts, #short_with_status_parts)
95-
local short = table.concat(short_with_status_parts, " ")
96-
return short:sub(2)
65+
return M.parse_status(line) ~= "none"
9766
end
9867

99-
---@params line string
100-
---@params path string
101-
M.line_to_result = function(line, path, package)
102-
if not string.find(line, "%[TestEventLogger%]") then
103-
return {}
104-
end
105-
106-
if not string.find(line, escape_magic_chars(package)) then
107-
return {}
108-
end
109-
110-
if not string.match(line, "(PASSED)$") and not string.match(line, "(SKIPPED)$") and not string.match(line, "(FAILED)$") then
111-
return {}
112-
end
113-
114-
local id = M.make_result_id(line, path)
115-
local status = M.get_result_type(line, package)
116-
local short = M.get_result_short(line)
117-
118-
return {
119-
id = id,
120-
status = status,
121-
short = short,
122-
}
68+
---@class TestResult
69+
---@field id string neotest id
70+
---@field status string passed, skipped, failed, none
71+
72+
---@param line string test output line
73+
---@param path string path to file
74+
---@param package string fully qualified class name
75+
---@return TestResult?
76+
M.parse_line = function(line, path, package)
77+
if not M.is_valid_gradle_test_line(line, package) then
78+
return nil
79+
end
80+
81+
local id = M.parse_test_id(line, path, package)
82+
if not id then
83+
return nil
84+
end
85+
86+
return {
87+
id = id,
88+
status = M.parse_status(line),
89+
}
12390
end
12491

125-
M.lines_to_results = function(lines, path, package)
126-
local results = {}
127-
128-
for _, line in ipairs(lines) do
129-
local result = M.line_to_result(line, path, package)
130-
if not result.id then
131-
-- noop
132-
else
133-
results[result.id] = result
134-
end
135-
end
136-
137-
return results
92+
---Converts lines of gradle output to test results
93+
---@param lines string[]
94+
---@param path string
95+
---@param package string
96+
---@return table<string, neotest.Result>
97+
M.parse_lines = function(lines, path, package)
98+
local results = {}
99+
100+
for _, line in ipairs(lines) do
101+
local result = M.parse_line(line, path, package)
102+
if result ~= nil then
103+
results[result.id] = result
104+
end
105+
end
106+
107+
return results
138108
end
139109

140110
return M

0 commit comments

Comments
 (0)