Skip to content

Commit dc4c525

Browse files
firelizzard18gopherbot
authored andcommitted
gopls/internal: test discovery
Implements test discovery. Tests are discovered as part of the type checking process, at the same time as method sets and xrefs, and cached. Does not implement the Modules command. Adds static detection of simple subtests. This provides a framework for static analysis of subtests but intentionally does not support more than the most trivial case in order to minimize the complexity of this CL. Fixes golang/go#59445. Updates golang/go#59445, golang/vscode-go#1602, golang/vscode-go#2445. Change-Id: Ief497977da09a1e07831e6c5f3b7d28d6874fd9f Reviewed-on: https://go-review.googlesource.com/c/tools/+/548675 Reviewed-by: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 8ba9169 commit dc4c525

File tree

11 files changed

+1025
-56
lines changed

11 files changed

+1025
-56
lines changed

gopls/internal/cache/check.go

+1
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ func storePackageResults(ctx context.Context, ph *packageHandle, p *Package) {
601601
toCache := map[string][]byte{
602602
xrefsKind: p.pkg.xrefs(),
603603
methodSetsKind: p.pkg.methodsets().Encode(),
604+
testsKind: p.pkg.tests().Encode(),
604605
diagnosticsKind: encodeDiagnostics(p.pkg.diagnostics),
605606
}
606607

gopls/internal/cache/package.go

+11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"golang.org/x/tools/gopls/internal/cache/metadata"
1616
"golang.org/x/tools/gopls/internal/cache/methodsets"
1717
"golang.org/x/tools/gopls/internal/cache/parsego"
18+
"golang.org/x/tools/gopls/internal/cache/testfuncs"
1819
"golang.org/x/tools/gopls/internal/cache/xrefs"
1920
"golang.org/x/tools/gopls/internal/protocol"
2021
)
@@ -60,6 +61,9 @@ type syntaxPackage struct {
6061

6162
methodsetsOnce sync.Once
6263
_methodsets *methodsets.Index // only used by the methodsets method
64+
65+
testsOnce sync.Once
66+
_tests *testfuncs.Index // only used by the tests method
6367
}
6468

6569
func (p *syntaxPackage) xrefs() []byte {
@@ -76,6 +80,13 @@ func (p *syntaxPackage) methodsets() *methodsets.Index {
7680
return p._methodsets
7781
}
7882

83+
func (p *syntaxPackage) tests() *testfuncs.Index {
84+
p.testsOnce.Do(func() {
85+
p._tests = testfuncs.NewIndex(p.compiledGoFiles, p.typesInfo)
86+
})
87+
return p._tests
88+
}
89+
7990
func (p *Package) String() string { return string(p.metadata.ID) }
8091

8192
func (p *Package) Metadata() *metadata.Package { return p.metadata }

gopls/internal/cache/snapshot.go

+28
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"golang.org/x/tools/gopls/internal/cache/metadata"
3232
"golang.org/x/tools/gopls/internal/cache/methodsets"
3333
"golang.org/x/tools/gopls/internal/cache/parsego"
34+
"golang.org/x/tools/gopls/internal/cache/testfuncs"
3435
"golang.org/x/tools/gopls/internal/cache/typerefs"
3536
"golang.org/x/tools/gopls/internal/cache/xrefs"
3637
"golang.org/x/tools/gopls/internal/file"
@@ -572,6 +573,7 @@ func (s *Snapshot) Overlays() []*overlay {
572573
const (
573574
xrefsKind = "xrefs"
574575
methodSetsKind = "methodsets"
576+
testsKind = "tests"
575577
exportDataKind = "export"
576578
diagnosticsKind = "diagnostics"
577579
typerefsKind = "typerefs"
@@ -673,6 +675,32 @@ func (s *Snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methods
673675
return indexes, s.forEachPackage(ctx, ids, pre, post)
674676
}
675677

678+
// Tests returns test-set indexes for the specified packages. There is a
679+
// one-to-one correspondence between ID and Index.
680+
//
681+
// If these indexes cannot be loaded from cache, the requested packages may be
682+
// type-checked.
683+
func (s *Snapshot) Tests(ctx context.Context, ids ...PackageID) ([]*testfuncs.Index, error) {
684+
ctx, done := event.Start(ctx, "cache.snapshot.Tests")
685+
defer done()
686+
687+
indexes := make([]*testfuncs.Index, len(ids))
688+
pre := func(i int, ph *packageHandle) bool {
689+
data, err := filecache.Get(testsKind, ph.key)
690+
if err == nil { // hit
691+
indexes[i] = testfuncs.Decode(data)
692+
return false
693+
} else if err != filecache.ErrNotFound {
694+
event.Error(ctx, "reading tests from filecache", err)
695+
}
696+
return true
697+
}
698+
post := func(i int, pkg *Package) {
699+
indexes[i] = pkg.pkg.tests()
700+
}
701+
return indexes, s.forEachPackage(ctx, ids, pre, post)
702+
}
703+
676704
// MetadataForFile returns a new slice containing metadata for each
677705
// package containing the Go file identified by uri, ordered by the
678706
// number of CompiledGoFiles (i.e. "narrowest" to "widest" package),
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package testfuncs
6+
7+
import (
8+
"fmt"
9+
"strconv"
10+
"strings"
11+
)
12+
13+
// The functions in this file are copies of those from the testing package.
14+
//
15+
// https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/testing/match.go
16+
17+
// uniqueName creates a unique name for the given parent and subname by affixing
18+
// it with one or more counts, if necessary.
19+
func (b *indexBuilder) uniqueName(parent, subname string) string {
20+
base := parent + "/" + subname
21+
22+
for {
23+
n := b.subNames[base]
24+
if n < 0 {
25+
panic("subtest count overflow")
26+
}
27+
b.subNames[base] = n + 1
28+
29+
if n == 0 && subname != "" {
30+
prefix, nn := parseSubtestNumber(base)
31+
if len(prefix) < len(base) && nn < b.subNames[prefix] {
32+
// This test is explicitly named like "parent/subname#NN",
33+
// and #NN was already used for the NNth occurrence of "parent/subname".
34+
// Loop to add a disambiguating suffix.
35+
continue
36+
}
37+
return base
38+
}
39+
40+
name := fmt.Sprintf("%s#%02d", base, n)
41+
if b.subNames[name] != 0 {
42+
// This is the nth occurrence of base, but the name "parent/subname#NN"
43+
// collides with the first occurrence of a subtest *explicitly* named
44+
// "parent/subname#NN". Try the next number.
45+
continue
46+
}
47+
48+
return name
49+
}
50+
}
51+
52+
// parseSubtestNumber splits a subtest name into a "#%02d"-formatted int
53+
// suffix (if present), and a prefix preceding that suffix (always).
54+
func parseSubtestNumber(s string) (prefix string, nn int) {
55+
i := strings.LastIndex(s, "#")
56+
if i < 0 {
57+
return s, 0
58+
}
59+
60+
prefix, suffix := s[:i], s[i+1:]
61+
if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') {
62+
// Even if suffix is numeric, it is not a possible output of a "%02" format
63+
// string: it has either too few digits or too many leading zeroes.
64+
return s, 0
65+
}
66+
if suffix == "00" {
67+
if !strings.HasSuffix(prefix, "/") {
68+
// We only use "#00" as a suffix for subtests named with the empty
69+
// string — it isn't a valid suffix if the subtest name is non-empty.
70+
return s, 0
71+
}
72+
}
73+
74+
n, err := strconv.ParseInt(suffix, 10, 32)
75+
if err != nil || n < 0 {
76+
return s, 0
77+
}
78+
return prefix, int(n)
79+
}
80+
81+
// rewrite rewrites a subname to having only printable characters and no white
82+
// space.
83+
func rewrite(s string) string {
84+
b := []byte{}
85+
for _, r := range s {
86+
switch {
87+
case isSpace(r):
88+
b = append(b, '_')
89+
case !strconv.IsPrint(r):
90+
s := strconv.QuoteRune(r)
91+
b = append(b, s[1:len(s)-1]...)
92+
default:
93+
b = append(b, string(r)...)
94+
}
95+
}
96+
return string(b)
97+
}
98+
99+
func isSpace(r rune) bool {
100+
if r < 0x2000 {
101+
switch r {
102+
// Note: not the same as Unicode Z class.
103+
case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:
104+
return true
105+
}
106+
} else {
107+
if r <= 0x200a {
108+
return true
109+
}
110+
switch r {
111+
case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000:
112+
return true
113+
}
114+
}
115+
return false
116+
}

0 commit comments

Comments
 (0)