all repos

utest.nvim @ db3688a

test runner that you shouldn't be using
2 files changed, 195 insertions(+), 32 deletions(-)
improve golang adapter thanks fredrikaverpil/neotest-golang
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-03-30 21:00:24 +0300
Change ID: psyzwxuolwkqupxyvzzynqwsmpzoxvqu
Parent: 8b770ef
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