2 files changed,
195 insertions(+),
32 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-03-30 21:00:24 +0300
Change ID:
psyzwxuolwkqupxyvzzynqwsmpzoxvqu
Parent:
8b770ef
jump to
| M | lua/utest.lua |
| M | lua/utest/golang.lua |
M
lua/utest.lua
··· 504 504 local root = tree:root() 505 505 local tests = {} 506 506 507 - -- TODO: this is probably overly complicated 508 507 for _, match, _ in query:iter_matches(root, bufnr, 0, -1) do 509 508 local test_name, test_def = nil, nil 510 509 for id, nodes in pairs(match) do ··· 536 535 }) 537 536 end 538 537 end 538 + 539 + -- Deduplicate: when multiple query patterns match overlapping AST regions 540 + -- (e.g. function_declaration + table test entry), keep the narrower match 541 + table.sort(tests, function(a, b) 542 + if a.line ~= b.line then return a.line < b.line end 543 + return (a.end_line - a.line) < (b.end_line - b.line) 544 + end) 545 + 546 + local seen = {} 547 + local deduped = {} 548 + for _, test in ipairs(tests) do 549 + local key = test.line .. ":" .. test.name 550 + if not seen[key] then 551 + seen[key] = true 552 + table.insert(deduped, test) 553 + end 554 + end 555 + tests = deduped 539 556 540 557 -- Resolve parent relationships for subtests (including nested subtests) 541 558 -- Uses line ranges to determine proper parent hierarchy
M
lua/utest/golang.lua
··· 1 1 local golang = {} 2 2 golang.ft = "go" 3 + 4 +-- source: https://github.com/fredrikaverpil/neotest-golang/blob/main/lua/neotest-golang/queries/go 3 5 golang.query = [[ 4 -; func TestXxx(t *testing.T) 6 +; func TestXxx(t *testing.T) / func ExampleXxx() 5 7 ((function_declaration 6 8 name: (identifier) @test.name) 7 - (#match? @test.name "^Test") 9 + (#match? @test.name "^(Test|Example)") 8 10 (#not-match? @test.name "^TestMain$")) @test.definition 9 11 10 -; t.Run("subtest name", func(t *testing.T) {...}) 12 +; t.Run("subtest", func(t *testing.T) {...}) 11 13 (call_expression 12 14 function: (selector_expression 13 15 operand: (identifier) @_operand ··· 16 18 (#match? @_method "^Run$") 17 19 arguments: (argument_list . (interpreted_string_literal) @test.name)) @test.definition 18 20 19 -; ============================================================================ 20 -; Table-driven tests with named slice variable and keyed fields 21 -; Detects table tests with struct fields using keys (e.g., {name: "test1"}). 22 -; Pattern: 23 -; tt := []struct{ name string }{ 24 -; {name: "test1"}, // @test.name = "test1" 25 -; {name: "test2"}, // @test.name = "test2" 26 -; } 27 -; for _, tc := range tt { 28 -; t.Run(tc.name, func(t *testing.T) { ... }) 29 -; } 21 +; Table tests: named variable, keyed fields 22 +; tt := []struct{ name string }{ {name: "test1"} } 23 +; for _, tc := range tt { t.Run(tc.name, ...) } 30 24 (block 31 25 (statement_list 32 26 (short_var_declaration 33 - left: (expression_list (identifier) @test.cases) 27 + left: (expression_list 28 + (identifier) @test.cases) 34 29 right: (expression_list 35 30 (composite_literal 36 31 (literal_value 37 32 (literal_element 38 33 (literal_value 39 34 (keyed_element 40 - (literal_element (identifier) @test.field.name) 41 - (literal_element (interpreted_string_literal) @test.name)))) @test.definition)))) 35 + (literal_element 36 + (identifier) @test.field.name) 37 + (literal_element 38 + (interpreted_string_literal) @test.name)))) @test.definition)))) 42 39 (for_statement 43 40 (range_clause 44 - left: (expression_list (identifier) @test.case) 41 + left: (expression_list 42 + (identifier) @test.case) 45 43 right: (identifier) @test.cases1 46 44 (#eq? @test.cases @test.cases1)) 47 45 body: (block ··· 60 58 field: (field_identifier) @test.field.name1 61 59 (#eq? @test.field.name @test.field.name1)))))))))) 62 60 63 -; ============================================================================ 64 -; Map-based table-driven tests 65 -; Detects table tests where test cases are defined in a map with string keys. 66 -; Pattern: 67 -; testCases := map[string]struct{ want int }{ 68 -; "test1": {want: 1}, // @test.name = "test1" 69 -; "test2": {want: 2}, // @test.name = "test2" 61 +; Table tests: named variable, unkeyed (positional) fields 62 +; tt := []struct{ name string }{ {"test1"} } 63 +; for _, tc := range tt { t.Run(tc.name, ...) } 64 +(block 65 + (statement_list 66 + (short_var_declaration 67 + left: (expression_list 68 + (identifier) @test.cases) 69 + right: (expression_list 70 + (composite_literal 71 + body: (literal_value 72 + (literal_element 73 + (literal_value 74 + . 75 + (literal_element 76 + (interpreted_string_literal) @test.name) 77 + (literal_element)) @test.definition))))) 78 + (for_statement 79 + (range_clause 80 + left: (expression_list 81 + (identifier) @test.key.name 82 + (identifier) @test.case) 83 + right: (identifier) @test.cases1 84 + (#eq? @test.cases @test.cases1)) 85 + body: (block 86 + (statement_list 87 + (expression_statement 88 + (call_expression 89 + function: (selector_expression 90 + operand: (identifier) @test.operand 91 + (#match? @test.operand "^[t]$") 92 + field: (field_identifier) @test.method 93 + (#match? @test.method "^Run$")) 94 + arguments: (argument_list 95 + (selector_expression 96 + operand: (identifier) @test.case1 97 + (#eq? @test.case @test.case1)))))))))) 98 + 99 +; Inline table tests: keyed fields 100 +; for _, tc := range []struct{ name string }{ {name: "test1"} } { 101 +; t.Run(tc.name, ...) 70 102 ; } 71 -; for name, tc := range testCases { 72 -; t.Run(name, func(t *testing.T) { ... }) 103 +(for_statement 104 + (range_clause 105 + left: (expression_list 106 + (identifier) 107 + (identifier) @test.case) 108 + right: (composite_literal 109 + type: (slice_type 110 + element: (struct_type 111 + (field_declaration_list 112 + (field_declaration 113 + name: (field_identifier) 114 + type: (type_identifier))))) 115 + body: (literal_value 116 + (literal_element 117 + (literal_value 118 + (keyed_element 119 + (literal_element 120 + (identifier)) @test.field.name 121 + (literal_element 122 + (interpreted_string_literal) @test.name))) @test.definition)))) 123 + body: (block 124 + (statement_list 125 + (expression_statement 126 + (call_expression 127 + function: (selector_expression 128 + operand: (identifier) 129 + field: (field_identifier)) 130 + arguments: (argument_list 131 + (selector_expression 132 + operand: (identifier) 133 + field: (field_identifier) @test.field.name1) 134 + (#eq? @test.field.name @test.field.name1))))))) 135 + 136 +; Inline table tests: unkeyed (positional) fields 137 +; for _, tc := range []struct{ name string }{ {"test1"} } { 138 +; t.Run(tc.name, ...) 73 139 ; } 140 +(for_statement 141 + (range_clause 142 + left: (expression_list 143 + (identifier) 144 + (identifier) @test.case) 145 + right: (composite_literal 146 + type: (slice_type 147 + element: (struct_type 148 + (field_declaration_list 149 + (field_declaration 150 + name: (field_identifier) @test.field.name 151 + type: (type_identifier) @field.type 152 + (#eq? @field.type "string"))))) 153 + body: (literal_value 154 + (literal_element 155 + (literal_value 156 + . 157 + (literal_element 158 + (interpreted_string_literal) @test.name) 159 + (literal_element)) @test.definition)))) 160 + body: (block 161 + (statement_list 162 + (expression_statement 163 + (call_expression 164 + function: (selector_expression 165 + operand: (identifier) @test.operand 166 + (#match? @test.operand "^[t]$") 167 + field: (field_identifier) @test.method 168 + (#match? @test.method "^Run$")) 169 + arguments: (argument_list 170 + (selector_expression 171 + operand: (identifier) @test.case1 172 + (#eq? @test.case @test.case1) 173 + field: (field_identifier) @test.field.name1 174 + (#eq? @test.field.name @test.field.name1)))))))) 175 + 176 +; Inline pointer slice table tests: keyed fields 177 +; for _, tc := range []*Type{ {Name: "test1"} } { 178 +; t.Run(tc.Name, ...) 179 +; } 180 +(for_statement 181 + (range_clause 182 + left: (expression_list 183 + (identifier) 184 + (identifier) @test.case) 185 + right: (composite_literal 186 + type: (slice_type 187 + element: (pointer_type)) 188 + body: (literal_value 189 + (literal_element 190 + (literal_value 191 + (keyed_element 192 + (literal_element 193 + (identifier) @test.field.name) 194 + (literal_element 195 + (interpreted_string_literal) @test.name))) @test.definition)))) 196 + body: (block 197 + (statement_list 198 + (expression_statement 199 + (call_expression 200 + function: (selector_expression 201 + operand: (identifier) @test.operand 202 + (#match? @test.operand "^[t]$") 203 + field: (field_identifier) @test.method 204 + (#match? @test.method "^Run$")) 205 + arguments: (argument_list 206 + (selector_expression 207 + operand: (identifier) @test.case1 208 + (#eq? @test.case @test.case1) 209 + field: (field_identifier) @test.field.name1 210 + (#eq? @test.field.name @test.field.name1)))))))) 211 + 212 +; Map-based table tests 213 +; testCases := map[string]struct{ want int }{ 214 +; "test1": {want: 1}, 215 +; } 216 +; for name, tc := range testCases { t.Run(name, ...) } 74 217 (block 75 218 (statement_list 76 219 (short_var_declaration 77 - left: (expression_list (identifier) @test.cases) 220 + left: (expression_list 221 + (identifier) @test.cases) 78 222 right: (expression_list 79 223 (composite_literal 80 224 (literal_value 81 225 (keyed_element 82 - (literal_element (interpreted_string_literal) @test.name) 83 - (literal_element (literal_value) @test.definition)))))) 226 + (literal_element 227 + (interpreted_string_literal) @test.name) 228 + (literal_element 229 + (literal_value) @test.definition)))))) 84 230 (for_statement 85 231 (range_clause 86 232 left: (expression_list ··· 105 251 ---@param name string 106 252 ---@return boolean 107 253 function golang.is_subtest(name) 108 - return not name:match "^Test" 254 + return not name:match "^Test" and not name:match "^Example" 109 255 end 110 256 111 257 ---@param file string