internal/lsp: add a test to make sure we handle bad imports

There was a regression where gopls would not type-check any package with
a bad import. This change fixes the regression and adds a test to make
sure it doesn't happen again.

Change-Id: I3acf0917d46e9444c20135559f057f0ecd20e15b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/201539
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-10-16 19:09:37 -04:00
parent e4d7c6f25b
commit de666e9706
12 changed files with 48 additions and 24 deletions

View File

@ -20,7 +20,7 @@ func TestMarker(t *testing.T) {
t.Fatal(err)
}
const expectNotes = 12
const expectNotes = 13
expectMarkers := map[string]string{
"αSimpleMarker": "α",
"OffsetMarker": "β",
@ -32,6 +32,7 @@ func TestMarker(t *testing.T) {
"Comment": "ι",
"LineComment": "someFunc",
"NonIdentifier": "+",
"StringMarker": "\"hello\"",
}
expectChecks := map[string][]interface{}{
"αSimpleMarker": nil,
@ -44,7 +45,7 @@ func TestMarker(t *testing.T) {
for name, tok := range expectMarkers {
offset := bytes.Index(content, []byte(tok))
markers[name] = token.Pos(offset + 1)
end := bytes.Index(content[offset+1:], []byte(tok))
end := bytes.Index(content[offset:], []byte(tok))
if end > 0 {
markers[name+"@"] = token.Pos(offset + end + 2)
}
@ -130,10 +131,6 @@ func checkMarker(t *testing.T, fset *token.FileSet, readFile expect.ReadFile, ma
if start != expectStart {
t.Errorf("%v: Expected %v got %v", fset.Position(pos), fset.Position(expectStart), fset.Position(start))
}
// Don't check the end for the LineComment test.
if name == "LineComment" {
return
}
if expectEnd, ok := markers[name+"@"]; ok && end != expectEnd {
t.Errorf("%v: Expected end %v got %v", fset.Position(pos), fset.Position(expectEnd), fset.Position(end))
}

View File

@ -19,6 +19,7 @@ const (
)
/*Marker ι inside ι a comment*/ //@mark(Comment,"ι inside ")
var x = "hello" //@mark(StringMarker, `"hello"`)
// someFunc is a function. //@mark(LineComment, "someFunc")
func someFunc(a, b int) int {

View File

@ -1057,6 +1057,10 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
// status if there's a dependency on a package that doesn't exist. But it should return
// a zero exit status and set an error on that package.
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") {
// Don't clobber stdout if `go list` actually returned something.
if len(stdout.String()) > 0 {
return stdout, nil
}
// try to extract package name from string
stderrStr := stderr.String()
var importPath string

View File

@ -133,10 +133,10 @@ func (s *snapshot) actionHandle(ctx context.Context, id packageID, mode source.P
return ah, nil
}
func (ah *actionHandle) analyze(ctx context.Context) ([]*analysis.Diagnostic, interface{}, error) {
v := ah.handle.Get(ctx)
func (act *actionHandle) analyze(ctx context.Context) ([]*analysis.Diagnostic, interface{}, error) {
v := act.handle.Get(ctx)
if v == nil {
return nil, nil, errors.Errorf("no analyses for %s", ah.pkg.ID())
return nil, nil, errors.Errorf("no analyses for %s", act.pkg.ID())
}
data := v.(*actionData)
return data.diagnostics, data.result, data.err

View File

@ -18,6 +18,7 @@ import (
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
)
@ -107,7 +108,6 @@ func (imp *importer) buildKey(ctx context.Context, id packageID, mode source.Par
if m == nil {
return nil, errors.Errorf("no metadata for %s", id)
}
phs, err := imp.parseGoHandles(ctx, m, mode)
if err != nil {
return nil, err
@ -135,7 +135,12 @@ func (imp *importer) buildKey(ctx context.Context, id packageID, mode source.Par
for _, dep := range deps {
depHandle, err := depImporter.checkPackageHandle(ctx, dep)
if err != nil {
return nil, errors.Errorf("no dep handle for %s: %+v", dep, err)
log.Error(ctx, "no dep handle", err, telemetry.Package.Of(dep))
// One bad dependency should not prevent us from checking the entire package.
// Add a special key to mark a bad dependency.
depKeys = append(depKeys, []byte(fmt.Sprintf("%s import not found", id)))
continue
}
cph.imports[depHandle.m.pkgPath] = depHandle.m.id
depKeys = append(depKeys, depHandle.key)

View File

@ -55,6 +55,10 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti
if diag.Range.Start.Character == 0 {
expect = fmt.Sprintf("%v:%v: %v", diag.URI.Filename(), diag.Range.Start.Line+1, diag.Message)
}
// Skip the badimport test for now, until we do a better job with diagnostic ranges.
if strings.Contains(diag.URI.Filename(), "badimport") {
continue
}
_, found := got[expect]
if !found {
t.Errorf("missing diagnostic %q", expect)
@ -63,6 +67,10 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti
}
}
for extra := range got {
// Skip the badimport test for now, until we do a better job with diagnostic ranges.
if strings.Contains(extra, "badimport") {
continue
}
t.Errorf("extra diagnostic %q", extra)
}
}

View File

@ -88,7 +88,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti
}
return
}
if diff := tests.DiffDiagnostics(uri, want, got); diff != "" {
if diff := tests.DiffDiagnostics(want, got); diff != "" {
t.Error(diff)
}
}

View File

@ -80,7 +80,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti
}
return
}
if diff := tests.DiffDiagnostics(uri, want, got); diff != "" {
if diff := tests.DiffDiagnostics(want, got); diff != "" {
t.Error(diff)
}
}

View File

@ -0,0 +1,5 @@
package bad
import (
_ "nosuchpkg" //@diag("_", "LSP", "could not import nosuchpkg (no package data for import path nosuchpkg)")
)

View File

@ -6,7 +6,7 @@ DeepCompletionsCount = 5
FuzzyCompletionsCount = 7
RankedCompletionsCount = 1
CaseSensitiveCompletionsCount = 4
DiagnosticsCount = 21
DiagnosticsCount = 22
FoldingRangesCount = 2
FormatCount = 6
ImportCount = 2

View File

@ -13,7 +13,7 @@ import (
// DiffDiagnostics prints the diff between expected and actual diagnostics test
// results.
func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
func DiffDiagnostics(want, got []source.Diagnostic) string {
sortDiagnostics(want)
sortDiagnostics(got)
@ -25,11 +25,21 @@ func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
if w.Message != g.Message {
return summarizeDiagnostics(i, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
}
if w.Severity != g.Severity {
return summarizeDiagnostics(i, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
}
if w.Source != g.Source {
return summarizeDiagnostics(i, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
}
// Don't check the range on the badimport test.
if strings.Contains(string(g.URI), "badimport") {
continue
}
if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
return summarizeDiagnostics(i, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
}
// Special case for diagnostics on parse errors.
if strings.Contains(string(uri), "noparse") {
if strings.Contains(string(g.URI), "noparse") {
if protocol.ComparePosition(g.Range.Start, g.Range.End) != 0 || protocol.ComparePosition(w.Range.Start, g.Range.End) != 0 {
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.Start)
}
@ -38,12 +48,6 @@ func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
}
}
if w.Severity != g.Severity {
return summarizeDiagnostics(i, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
}
if w.Source != g.Source {
return summarizeDiagnostics(i, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
}
}
return ""
}

View File

@ -257,12 +257,12 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
filename := data.Exported.File(testModule, fragment)
data.fragments[filename] = fragment
}
data.Exported.Config.Logf = nil
data.Exported.Config.Logf = t.Logf
data.Config.Logf = t.Logf
// Merge the exported.Config with the view.Config.
data.Config = *data.Exported.Config
data.Config.Fset = token.NewFileSet()
data.Config.Logf = nil
data.Config.Context = Context(nil)
data.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
panic("ParseFile should not be called")