Skip to content

Commit 593de60

Browse files
committed
go/packages: handle an overlay edge case with test variants
As usual, in debugging the creation of a new file with gopls, I've encountered a go/packages overlay bug. The issue is: A file b/b.go with package name "b" exists on disk. A package b/b_test.go with no content exists on disk. There is an overlay for b/b_test.go that contains the package name "b". Running packages.Load for file=b/b_test.go will result in a failure to load package b [b.test]. This change adds this test to the go/packages tests. This case is fixed by restricting the fallback logic in runContainsQueries. We only attempt to construct an ad-hoc package if the original package was returned with no GoFiles. Also, a minor change to the gopls error parsing code that fixes a case in which diagnostics were being sent without corresponding files. Updates golang/go#36635. Change-Id: I38680a2cc65ae9c3252294db6b942d031189faf5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/215743 Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent ba161d9 commit 593de60

File tree

4 files changed

+143
-43
lines changed

4 files changed

+143
-43
lines changed

go/packages/golist.go

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -273,43 +273,16 @@ func (state *golistState) runContainsQueries(response *responseDeduper, queries
273273
return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
274274
}
275275
dirResponse, err := state.createDriverResponse(pattern)
276-
if err != nil || (len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1) {
277-
// There was an error loading the package. Try to load the file as an ad-hoc package.
278-
// Usually the error will appear in a returned package, but may not if we're in modules mode
279-
// and the ad-hoc is located outside a module.
276+
277+
// If there was an error loading the package, or the package is returned
278+
// with errors, try to load the file as an ad-hoc package.
279+
// Usually the error will appear in a returned package, but may not if we're
280+
// in module mode and the ad-hoc is located outside a module.
281+
if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 &&
282+
len(dirResponse.Packages[0].Errors) == 1 {
280283
var queryErr error
281-
dirResponse, queryErr = state.createDriverResponse(query)
282-
if queryErr != nil {
283-
// Return the original error if the attempt to fall back failed.
284-
return err
285-
}
286-
// If we get nothing back from `go list`, try to make this file into its own ad-hoc package.
287-
if len(dirResponse.Packages) == 0 && queryErr == nil {
288-
dirResponse.Packages = append(dirResponse.Packages, &Package{
289-
ID: "command-line-arguments",
290-
PkgPath: query,
291-
GoFiles: []string{query},
292-
CompiledGoFiles: []string{query},
293-
Imports: make(map[string]*Package),
294-
})
295-
dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments")
296-
}
297-
// Special case to handle issue #33482:
298-
// If this is a file= query for ad-hoc packages where the file only exists on an overlay,
299-
// and exists outside of a module, add the file in for the package.
300-
if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" ||
301-
filepath.ToSlash(dirResponse.Packages[0].PkgPath) == filepath.ToSlash(query)) {
302-
if len(dirResponse.Packages[0].GoFiles) == 0 {
303-
filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
304-
// TODO(matloob): check if the file is outside of a root dir?
305-
for path := range state.cfg.Overlay {
306-
if path == filename {
307-
dirResponse.Packages[0].Errors = nil
308-
dirResponse.Packages[0].GoFiles = []string{path}
309-
dirResponse.Packages[0].CompiledGoFiles = []string{path}
310-
}
311-
}
312-
}
284+
if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil {
285+
return err // return the original error
313286
}
314287
}
315288
isRoot := make(map[string]bool, len(dirResponse.Roots))
@@ -337,6 +310,49 @@ func (state *golistState) runContainsQueries(response *responseDeduper, queries
337310
return nil
338311
}
339312

313+
// adhocPackage attempts to load or construct an ad-hoc package for a given
314+
// query, if the original call to the driver produced inadequate results.
315+
func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) {
316+
response, err := state.createDriverResponse(query)
317+
if err != nil {
318+
return nil, err
319+
}
320+
// If we get nothing back from `go list`,
321+
// try to make this file into its own ad-hoc package.
322+
// TODO(rstambler): Should this check against the original response?
323+
if len(response.Packages) == 0 {
324+
response.Packages = append(response.Packages, &Package{
325+
ID: "command-line-arguments",
326+
PkgPath: query,
327+
GoFiles: []string{query},
328+
CompiledGoFiles: []string{query},
329+
Imports: make(map[string]*Package),
330+
})
331+
response.Roots = append(response.Roots, "command-line-arguments")
332+
}
333+
// Handle special cases.
334+
if len(response.Packages) == 1 {
335+
// golang/go#33482: If this is a file= query for ad-hoc packages where
336+
// the file only exists on an overlay, and exists outside of a module,
337+
// add the file to the package and remove the errors.
338+
if response.Packages[0].ID == "command-line-arguments" ||
339+
filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) {
340+
if len(response.Packages[0].GoFiles) == 0 {
341+
filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
342+
// TODO(matloob): check if the file is outside of a root dir?
343+
for path := range state.cfg.Overlay {
344+
if path == filename {
345+
response.Packages[0].Errors = nil
346+
response.Packages[0].GoFiles = []string{path}
347+
response.Packages[0].CompiledGoFiles = []string{path}
348+
}
349+
}
350+
}
351+
}
352+
}
353+
return response, nil
354+
}
355+
340356
// Fields must match go list;
341357
// see $GOROOT/src/cmd/go/internal/load/pkg.go.
342358
type jsonPackage struct {

go/packages/golist_overlay.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif
116116
if isTestFile && !isXTest && testVariantOf != nil {
117117
pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
118118
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
119+
// Add the package under test and its imports to the test variant.
120+
pkg.forTest = testVariantOf.PkgPath
121+
for k, v := range testVariantOf.Imports {
122+
pkg.Imports[k] = &Package{ID: v.ID}
123+
}
119124
}
120125
}
121126
}

go/packages/packages114_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2020 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+
// +build go1.14
6+
7+
package packages_test
8+
9+
import (
10+
"fmt"
11+
"path/filepath"
12+
"testing"
13+
14+
"golang.org/x/tools/go/packages"
15+
"golang.org/x/tools/go/packages/packagestest"
16+
)
17+
18+
func TestInvalidFilesInOverlay(t *testing.T) { packagestest.TestAll(t, testInvalidFilesInOverlay) }
19+
func testInvalidFilesInOverlay(t *testing.T, exporter packagestest.Exporter) {
20+
exported := packagestest.Export(t, exporter, []packagestest.Module{
21+
{
22+
Name: "golang.org/fake",
23+
Files: map[string]interface{}{
24+
"d/d.go": `package d; import "net/http"; const d = http.MethodGet;`,
25+
"d/util.go": ``,
26+
"d/d_test.go": ``,
27+
},
28+
},
29+
})
30+
defer exported.Cleanup()
31+
32+
dir := filepath.Dir(filepath.Dir(exported.File("golang.org/fake", "d/d.go")))
33+
34+
// Additional tests for test variants.
35+
for i, tt := range []struct {
36+
name string
37+
overlay map[string][]byte
38+
want string // expected value of d.D
39+
40+
}{
41+
// Overlay with a test variant.
42+
{"test_variant",
43+
map[string][]byte{
44+
filepath.Join(dir, "d", "d_test.go"): []byte(`package d; import "testing"; const D = d + "_test"; func TestD(t *testing.T) {};`)},
45+
`"GET_test"`},
46+
// Overlay in package.
47+
{"second_file",
48+
map[string][]byte{
49+
filepath.Join(dir, "d", "util.go"): []byte(`package d; const D = d + "_util";`)},
50+
`"GET_util"`},
51+
} {
52+
t.Run(tt.name, func(t *testing.T) {
53+
exported.Config.Overlay = tt.overlay
54+
exported.Config.Mode = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
55+
packages.NeedDeps | packages.NeedTypes | packages.NeedTypesSizes
56+
exported.Config.Tests = true
57+
58+
for f := range tt.overlay {
59+
initial, err := packages.Load(exported.Config, fmt.Sprintf("file=%s", f))
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
d := initial[0]
64+
// Check value of d.D.
65+
dD := constant(d, "D")
66+
if dD == nil {
67+
t.Fatalf("%d. d.D: got nil", i)
68+
}
69+
got := dD.Val().String()
70+
if got != tt.want {
71+
t.Fatalf("%d. d.D: got %s, want %s", i, got, tt.want)
72+
}
73+
}
74+
})
75+
76+
}
77+
}

internal/lsp/cache/errors.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,18 @@ func sourceError(ctx context.Context, fset *token.FileSet, pkg *pkg, e interface
4444

4545
if e.Pos == "" {
4646
spn = parseGoListError(e.Msg)
47+
48+
// We may not have been able to parse a valid span.
49+
if _, err := spanToRange(ctx, pkg, spn); err != nil {
50+
return &source.Error{
51+
URI: spn.URI(),
52+
Message: msg,
53+
Kind: kind,
54+
}, nil
55+
}
4756
} else {
4857
spn = span.Parse(e.Pos)
4958
}
50-
// If the range can't be derived from the parseGoListError function, then we do not have a valid position.
51-
if _, err := spanToRange(ctx, pkg, spn); err != nil && e.Pos == "" {
52-
return &source.Error{
53-
Message: msg,
54-
Kind: kind,
55-
}, nil
56-
}
5759
case *scanner.Error:
5860
msg = e.Msg
5961
kind = source.ParseError

0 commit comments

Comments
 (0)