Skip to content

Commit abeba28

Browse files
committed
gopls/internal/regtest/marker: support proxy files in marker tests
Add new functionality to wire-in a file-based GOPROXY value for marker tests containing files starting with "proxy/". Use this to port an example regression test, adding better coverage for go.work support while doing so. To solve one of the main pain points from working with proxy files in the regtests, add a -write_sumfile=dir1,dir2 flag to the test framework, to allow auto-generation of go.sum files. Along the way, loosen the diagnostic matching logic, ignoring end positions. Change-Id: I8421cea807fd87dcbe6b1619720a46b3f1f7bc3f Reviewed-on: https://go-review.googlesource.com/c/tools/+/494396 TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Run-TryBot: Robert Findley <[email protected]>
1 parent 18186f0 commit abeba28

File tree

11 files changed

+146
-75
lines changed

11 files changed

+146
-75
lines changed

gopls/internal/lsp/fake/sandbox.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,11 @@ func (sb *Sandbox) goCommandInvocation() gocommand.Invocation {
254254
// RunGoCommand executes a go command in the sandbox. If checkForFileChanges is
255255
// true, the sandbox scans the working directory and emits file change events
256256
// for any file changes it finds.
257-
func (sb *Sandbox) RunGoCommand(ctx context.Context, dir, verb string, args []string, checkForFileChanges bool) error {
257+
func (sb *Sandbox) RunGoCommand(ctx context.Context, dir, verb string, args, env []string, checkForFileChanges bool) error {
258258
inv := sb.goCommandInvocation()
259259
inv.Verb = verb
260260
inv.Args = args
261+
inv.Env = append(inv.Env, env...)
261262
if dir != "" {
262263
inv.WorkingDir = sb.Workdir.AbsPath(dir)
263264
}
@@ -289,7 +290,7 @@ func (sb *Sandbox) GoVersion(ctx context.Context) (int, error) {
289290
func (sb *Sandbox) Close() error {
290291
var goCleanErr error
291292
if sb.gopath != "" {
292-
goCleanErr = sb.RunGoCommand(context.Background(), "", "clean", []string{"-modcache"}, false)
293+
goCleanErr = sb.RunGoCommand(context.Background(), "", "clean", []string{"-modcache"}, nil, false)
293294
}
294295
err := robustio.RemoveAll(sb.rootdir)
295296
if err != nil || goCleanErr != nil {

gopls/internal/lsp/regtest/marker.go

+63-24
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,14 @@ var update = flag.Bool("update", false, "if set, update test data during marker
9797
//
9898
// # Special files
9999
//
100-
// There are three types of file within the test archive that are given special
100+
// There are several types of file within the test archive that are given special
101101
// treatment by the test runner:
102102
// - "flags": this file is treated as a whitespace-separated list of flags
103103
// that configure the MarkerTest instance. Supported flags:
104104
// -min_go=go1.18 sets the minimum Go version for the test;
105105
// -cgo requires that CGO_ENABLED is set and the cgo tool is available
106+
// -write_sumfile=a,b,c instructs the test runner to generate go.sum files
107+
// in these directories before running the test.
106108
// TODO(rfindley): support flag values containing whitespace.
107109
// - "settings.json": this file is parsed as JSON, and used as the
108110
// session configuration (see gopls/doc/settings.md)
@@ -115,6 +117,9 @@ var update = flag.Bool("update", false, "if set, update test data during marker
115117
// Foo were of type *Golden, the test runner would convert the identifier a
116118
// in the call @foo(a, "b", 3) into a *Golden by collecting golden file
117119
// data starting with "@a/".
120+
// - proxy files: any file starting with proxy/ is treated as a Go proxy
121+
// file. If present, these files are written to a separate temporary
122+
// directory and GOPROXY is set to file://<proxy directory>.
118123
//
119124
// # Marker types
120125
//
@@ -136,10 +141,8 @@ var update = flag.Bool("update", false, "if set, update test data during marker
136141
// a 1:1 correspondence between observed diagnostics and diag annotations.
137142
// The diagnostics source and kind fields are ignored, to reduce fuss.
138143
//
139-
// The marker must accurately represent the diagnostic's range.
140-
// Use grouping parens in the location regular expression to indicate
141-
// a portion in context.
142-
// TODO(adonovan): make this less strict, like the old framework.
144+
// The specified location must match the start position of the diagnostic,
145+
// but end positions are ignored.
143146
//
144147
// TODO(adonovan): in the older marker framework, the annotation asserted
145148
// two additional fields (source="compiler", kind="error"). Restore them?
@@ -357,10 +360,17 @@ func RunMarkerTests(t *testing.T, dir string) {
357360
}
358361
config.Settings["diagnosticsDelay"] = "10ms"
359362
}
360-
run := &markerTestRun{
361-
test: test,
362-
env: newEnv(t, cache, test.files, config),
363363

364+
var writeGoSum []string
365+
if test.writeGoSum != "" {
366+
for _, d := range strings.Split(test.writeGoSum, ",") {
367+
writeGoSum = append(writeGoSum, strings.TrimSpace(d))
368+
}
369+
}
370+
371+
run := &markerTestRun{
372+
test: test,
373+
env: newEnv(t, cache, test.files, test.proxyFiles, writeGoSum, config),
364374
locations: make(map[expect.Identifier]protocol.Location),
365375
diags: make(map[protocol.Location][]protocol.Diagnostic),
366376
}
@@ -397,8 +407,11 @@ func RunMarkerTests(t *testing.T, dir string) {
397407
uri := run.env.Sandbox.Workdir.URI(path)
398408
for _, diag := range params.Diagnostics {
399409
loc := protocol.Location{
400-
URI: uri,
401-
Range: diag.Range,
410+
URI: uri,
411+
Range: protocol.Range{
412+
Start: diag.Range.Start,
413+
End: diag.Range.Start, // ignore end positions
414+
},
402415
}
403416
run.diags[loc] = append(run.diags[loc], diag)
404417
}
@@ -546,21 +559,23 @@ var markerFuncs = map[string]markerFunc{
546559
// See the documentation for RunMarkerTests for more information on the archive
547560
// format.
548561
type markerTest struct {
549-
name string // relative path to the txtar file in the testdata dir
550-
fset *token.FileSet // fileset used for parsing notes
551-
content []byte // raw test content
552-
archive *txtar.Archive // original test archive
553-
settings map[string]interface{} // gopls settings
554-
env map[string]string // editor environment
555-
files map[string][]byte // data files from the archive (excluding special files)
556-
notes []*expect.Note // extracted notes from data files
557-
golden map[string]*Golden // extracted golden content, by identifier name
562+
name string // relative path to the txtar file in the testdata dir
563+
fset *token.FileSet // fileset used for parsing notes
564+
content []byte // raw test content
565+
archive *txtar.Archive // original test archive
566+
settings map[string]interface{} // gopls settings
567+
env map[string]string // editor environment
568+
proxyFiles map[string][]byte // proxy content
569+
files map[string][]byte // data files from the archive (excluding special files)
570+
notes []*expect.Note // extracted notes from data files
571+
golden map[string]*Golden // extracted golden content, by identifier name
558572

559573
// flags holds flags extracted from the special "flags" archive file.
560574
flags []string
561575
// Parsed flags values.
562576
minGoVersion string
563577
cgo bool
578+
writeGoSum string // comma separated dirs to write go sum for
564579
}
565580

566581
// flagSet returns the flagset used for parsing the special "flags" file in the
@@ -569,6 +584,7 @@ func (t *markerTest) flagSet() *flag.FlagSet {
569584
flags := flag.NewFlagSet(t.name, flag.ContinueOnError)
570585
flags.StringVar(&t.minGoVersion, "min_go", "", "if set, the minimum go1.X version required for this test")
571586
flags.BoolVar(&t.cgo, "cgo", false, "if set, requires cgo (both the cgo tool and CGO_ENABLED=1)")
587+
flags.StringVar(&t.writeGoSum, "write_sumfile", "", "if set, write the sumfile for these directories")
572588
return flags
573589
}
574590

@@ -711,6 +727,13 @@ func loadMarkerTest(name string, content []byte) (*markerTest, error) {
711727
}
712728
test.golden[id].data[name] = file.Data
713729

730+
case strings.HasPrefix(file.Name, "proxy/"):
731+
name := file.Name[len("proxy/"):]
732+
if test.proxyFiles == nil {
733+
test.proxyFiles = make(map[string][]byte)
734+
}
735+
test.proxyFiles[name] = file.Data
736+
714737
default: // ordinary file content
715738
notes, err := expect.Parse(test.fset, file.Name, file.Data)
716739
if err != nil {
@@ -773,6 +796,8 @@ func formatTest(test *markerTest) ([]byte, error) {
773796
default:
774797
if _, ok := test.files[file.Name]; ok { // ordinary file
775798
arch.Files = append(arch.Files, file)
799+
} else if strings.HasPrefix(file.Name, "proxy/") { // proxy file
800+
arch.Files = append(arch.Files, file)
776801
} else if data, ok := updatedGolden[file.Name]; ok { // golden file
777802
arch.Files = append(arch.Files, txtar.File{Name: file.Name, Data: data})
778803
delete(updatedGolden, file.Name)
@@ -798,16 +823,22 @@ func formatTest(test *markerTest) ([]byte, error) {
798823
//
799824
// TODO(rfindley): simplify and refactor the construction of testing
800825
// environments across regtests, marker tests, and benchmarks.
801-
func newEnv(t *testing.T, cache *cache.Cache, files map[string][]byte, config fake.EditorConfig) *Env {
826+
func newEnv(t *testing.T, cache *cache.Cache, files, proxyFiles map[string][]byte, writeGoSum []string, config fake.EditorConfig) *Env {
802827
sandbox, err := fake.NewSandbox(&fake.SandboxConfig{
803-
RootDir: t.TempDir(),
804-
GOPROXY: "https://proxy.golang.org",
805-
Files: files,
828+
RootDir: t.TempDir(),
829+
Files: files,
830+
ProxyFiles: proxyFiles,
806831
})
807832
if err != nil {
808833
t.Fatal(err)
809834
}
810835

836+
for _, dir := range writeGoSum {
837+
if err := sandbox.RunGoCommand(context.Background(), dir, "list", []string{"-mod=mod", "..."}, []string{"GOWORK=off"}, true); err != nil {
838+
t.Fatal(err)
839+
}
840+
}
841+
811842
// Put a debug instance in the context to prevent logging to stderr.
812843
// See associated TODO in runner.go: we should revisit this pattern.
813844
ctx := context.Background()
@@ -851,7 +882,7 @@ type markerTestRun struct {
851882
// Collected information.
852883
// Each @diag/@suggestedfix marker eliminates an entry from diags.
853884
locations map[expect.Identifier]protocol.Location
854-
diags map[protocol.Location][]protocol.Diagnostic
885+
diags map[protocol.Location][]protocol.Diagnostic // diagnostics by position; location end == start
855886
}
856887

857888
// sprintf returns a formatted string after applying pre-processing to
@@ -1324,7 +1355,14 @@ func diagMarker(mark marker, loc protocol.Location, re *regexp.Regexp) {
13241355
}
13251356
}
13261357

1358+
// removeDiagnostic looks for a diagnostic matching loc at the given position.
1359+
//
1360+
// If found, it returns (diag, true), and eliminates the matched diagnostic
1361+
// from the unmatched set.
1362+
//
1363+
// If not found, it returns (protocol.Diagnostic{}, false).
13271364
func removeDiagnostic(mark marker, loc protocol.Location, re *regexp.Regexp) (protocol.Diagnostic, bool) {
1365+
loc.Range.End = loc.Range.Start // diagnostics ignore end position.
13281366
diags := mark.run.diags[loc]
13291367
for i, diag := range diags {
13301368
if re.MatchString(diag.Message) {
@@ -1444,6 +1482,7 @@ func codeActionErrMarker(mark marker, actionKind string, start, end protocol.Loc
14441482
// the expectation of a diagnostic, but then it applies the first code
14451483
// action of the specified kind suggested by the matched diagnostic.
14461484
func suggestedfixMarker(mark marker, loc protocol.Location, re *regexp.Regexp, actionKind string, golden *Golden) {
1485+
loc.Range.End = loc.Range.Start // diagnostics ignore end position.
14471486
// Find and remove the matching diagnostic.
14481487
diag, ok := removeDiagnostic(mark, loc, re)
14491488
if !ok {

gopls/internal/lsp/regtest/wrappers.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ func (e *Env) RunGenerate(dir string) {
256256
// directory.
257257
func (e *Env) RunGoCommand(verb string, args ...string) {
258258
e.T.Helper()
259-
if err := e.Sandbox.RunGoCommand(e.Ctx, "", verb, args, true); err != nil {
259+
if err := e.Sandbox.RunGoCommand(e.Ctx, "", verb, args, nil, true); err != nil {
260260
e.T.Fatal(err)
261261
}
262262
}
@@ -265,7 +265,7 @@ func (e *Env) RunGoCommand(verb string, args ...string) {
265265
// relative directory of the sandbox.
266266
func (e *Env) RunGoCommandInDir(dir, verb string, args ...string) {
267267
e.T.Helper()
268-
if err := e.Sandbox.RunGoCommand(e.Ctx, dir, verb, args, true); err != nil {
268+
if err := e.Sandbox.RunGoCommand(e.Ctx, dir, verb, args, nil, true); err != nil {
269269
e.T.Fatal(err)
270270
}
271271
}
@@ -286,7 +286,7 @@ func (e *Env) GoVersion() int {
286286
func (e *Env) DumpGoSum(dir string) {
287287
e.T.Helper()
288288

289-
if err := e.Sandbox.RunGoCommand(e.Ctx, dir, "list", []string{"-mod=mod", "..."}, true); err != nil {
289+
if err := e.Sandbox.RunGoCommand(e.Ctx, dir, "list", []string{"-mod=mod", "..."}, nil, true); err != nil {
290290
e.T.Fatal(err)
291291
}
292292
sumFile := path.Join(dir, "/go.sum")

gopls/internal/regtest/diagnostics/diagnostics_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func Hello() {
299299
InitialWorkspaceLoad,
300300
Diagnostics(env.AtRegexp("main.go", `"mod.com/bob"`)),
301301
)
302-
if err := env.Sandbox.RunGoCommand(env.Ctx, "", "mod", []string{"init", "mod.com"}, true); err != nil {
302+
if err := env.Sandbox.RunGoCommand(env.Ctx, "", "mod", []string{"init", "mod.com"}, nil, true); err != nil {
303303
t.Fatal(err)
304304
}
305305
env.AfterChange(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This test checks the suggested fix to remove unused require statements from
2+
go.mod files.
3+
4+
-- flags --
5+
-write_sumfile=a
6+
7+
-- proxy/[email protected]/x.go --
8+
package pkg
9+
const X = 1
10+
11+
-- a/go.mod --
12+
module mod.com
13+
14+
go 1.14
15+
16+
require example.com v1.0.0 //@suggestedfix("require", re"not used", "quickfix", a)
17+
18+
-- @a/a/go.mod --
19+
module mod.com
20+
21+
go 1.14
22+
-- a/main.go --
23+
package main
24+
func main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
This test checks the suggested fix to remove unused require statements from
2+
go.mod files, when a go.work file is used.
3+
4+
Note that unlike unusedrequire.txt, we need not write go.sum files when
5+
a go.work file is used.
6+
7+
-- flags --
8+
-min_go=go1.18
9+
10+
-- proxy/[email protected]/x.go --
11+
package pkg
12+
const X = 1
13+
14+
-- go.work --
15+
go 1.21
16+
17+
use (
18+
./a
19+
./b
20+
)
21+
-- a/go.mod --
22+
module mod.com/a
23+
24+
go 1.14
25+
26+
require example.com v1.0.0 //@suggestedfix("require", re"not used", "quickfix", a)
27+
28+
-- @a/a/go.mod --
29+
module mod.com/a
30+
31+
go 1.14
32+
-- a/main.go --
33+
package main
34+
func main() {}
35+
36+
-- b/go.mod --
37+
module mod.com/b
38+
39+
go 1.14
40+
41+
require example.com v1.0.0 //@suggestedfix("require", re"not used", "quickfix", b)
42+
43+
-- @b/b/go.mod --
44+
module mod.com/b
45+
46+
go 1.14
47+
-- b/main.go --
48+
package main
49+
func main() {}

gopls/internal/regtest/misc/definition_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ const _ = b.K
476476
}
477477

478478
// Run 'go mod vendor' outside the editor.
479-
if err := env.Sandbox.RunGoCommand(env.Ctx, ".", "mod", []string{"vendor"}, true); err != nil {
479+
if err := env.Sandbox.RunGoCommand(env.Ctx, ".", "mod", []string{"vendor"}, nil, true); err != nil {
480480
t.Fatalf("go mod vendor: %v", err)
481481
}
482482

gopls/internal/regtest/misc/references_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ var _ b.B
419419
checkVendor(env.Implementations(refLoc), false)
420420

421421
// Run 'go mod vendor' outside the editor.
422-
if err := env.Sandbox.RunGoCommand(env.Ctx, ".", "mod", []string{"vendor"}, true); err != nil {
422+
if err := env.Sandbox.RunGoCommand(env.Ctx, ".", "mod", []string{"vendor"}, nil, true); err != nil {
423423
t.Fatalf("go mod vendor: %v", err)
424424
}
425425

gopls/internal/regtest/modfile/modfile_test.go

-42
Original file line numberDiff line numberDiff line change
@@ -336,48 +336,6 @@ require example.com v1.2.3
336336
})
337337
}
338338

339-
func TestUnusedDiag(t *testing.T) {
340-
341-
const proxy = `
342-
343-
package pkg
344-
const X = 1
345-
`
346-
const files = `
347-
-- a/go.mod --
348-
module mod.com
349-
go 1.14
350-
require example.com v1.0.0
351-
-- a/go.sum --
352-
example.com v1.0.0 h1:38O7j5rEBajXk+Q5wzLbRN7KqMkSgEiN9NqcM1O2bBM=
353-
example.com v1.0.0/go.mod h1:vUsPMGpx9ZXXzECCOsOmYCW7npJTwuA16yl89n3Mgls=
354-
-- a/main.go --
355-
package main
356-
func main() {}
357-
`
358-
359-
const want = `module mod.com
360-
361-
go 1.14
362-
`
363-
364-
RunMultiple{
365-
{"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))},
366-
{"nested", WithOptions(ProxyFiles(proxy))},
367-
}.Run(t, files, func(t *testing.T, env *Env) {
368-
env.OpenFile("a/go.mod")
369-
var d protocol.PublishDiagnosticsParams
370-
env.AfterChange(
371-
Diagnostics(env.AtRegexp("a/go.mod", `require example.com`)),
372-
ReadDiagnostics("a/go.mod", &d),
373-
)
374-
env.ApplyQuickFixes("a/go.mod", d.Diagnostics)
375-
if got := env.BufferText("a/go.mod"); got != want {
376-
t.Fatalf("unexpected go.mod content:\n%s", compare.Text(want, got))
377-
}
378-
})
379-
}
380-
381339
// Test to reproduce golang/go#39041. It adds a new require to a go.mod file
382340
// that already has an unused require.
383341
func TestNewDepWithUnusedDep(t *testing.T) {

0 commit comments

Comments
 (0)