|
1 | 1 | local M = {}
|
2 | 2 |
|
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 |
31 | 18 | end
|
32 | 19 |
|
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 |
84 | 55 | end
|
85 | 56 |
|
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 |
89 | 64 |
|
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" |
97 | 66 | end
|
98 | 67 |
|
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 | + } |
123 | 90 | end
|
124 | 91 |
|
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 |
138 | 108 | end
|
139 | 109 |
|
140 | 110 | return M
|
0 commit comments