From 37215997d4fbf213daec8d214be63a144448d806 Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Thu, 6 Feb 2020 15:30:47 -0500 Subject: [PATCH] internal/lsp: move all of the test helpers functions into one file Each of these files had a couple of functions that were very similar in nature. There's no need to have separate files for all of these. Change-Id: I4ca648d1b7e90539f274871d45b7c97a8111631f Reviewed-on: https://go-review.googlesource.com/c/tools/+/218319 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot Reviewed-by: Heschi Kreinick --- internal/lsp/tests/completion.go | 197 ------------------ internal/lsp/tests/diagnostics.go | 66 ------ internal/lsp/tests/links.go | 55 ----- internal/lsp/tests/symbol.go | 59 ------ internal/lsp/tests/util.go | 334 ++++++++++++++++++++++++++++++ 5 files changed, 334 insertions(+), 377 deletions(-) delete mode 100644 internal/lsp/tests/completion.go delete mode 100644 internal/lsp/tests/diagnostics.go delete mode 100644 internal/lsp/tests/links.go delete mode 100644 internal/lsp/tests/symbol.go diff --git a/internal/lsp/tests/completion.go b/internal/lsp/tests/completion.go deleted file mode 100644 index 8ef13a6088..0000000000 --- a/internal/lsp/tests/completion.go +++ /dev/null @@ -1,197 +0,0 @@ -package tests - -import ( - "bytes" - "fmt" - "go/token" - "sort" - "strconv" - "strings" - - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/source" - "golang.org/x/tools/internal/span" -) - -func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem { - var result []protocol.CompletionItem - for _, item := range items { - result = append(result, ToProtocolCompletionItem(item)) - } - return result -} - -func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem { - pItem := protocol.CompletionItem{ - Label: item.Label, - Kind: item.Kind, - Detail: item.Detail, - Documentation: item.Documentation, - InsertText: item.InsertText, - TextEdit: &protocol.TextEdit{ - NewText: item.Snippet(), - }, - // Negate score so best score has lowest sort text like real API. - SortText: fmt.Sprint(-item.Score), - } - if pItem.InsertText == "" { - pItem.InsertText = pItem.Label - } - return pItem -} - -func FilterBuiltins(src span.Span, items []protocol.CompletionItem) []protocol.CompletionItem { - var ( - got []protocol.CompletionItem - wantBuiltins = strings.Contains(string(src.URI()), "builtins") - wantKeywords = strings.Contains(string(src.URI()), "keywords") - ) - for _, item := range items { - if !wantBuiltins && isBuiltin(item.Label, item.Detail, item.Kind) { - continue - } - - if !wantKeywords && token.Lookup(item.Label).IsKeyword() { - continue - } - - got = append(got, item) - } - return got -} - -func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool { - if detail == "" && kind == protocol.ClassCompletion { - return true - } - // Remaining builtin constants, variables, interfaces, and functions. - trimmed := label - if i := strings.Index(trimmed, "("); i >= 0 { - trimmed = trimmed[:i] - } - switch trimmed { - case "append", "cap", "close", "complex", "copy", "delete", - "error", "false", "imag", "iota", "len", "make", "new", - "nil", "panic", "print", "println", "real", "recover", "true": - return true - } - return false -} - -func CheckCompletionOrder(want, got []protocol.CompletionItem, strictScores bool) string { - var ( - matchedIdxs []int - lastGotIdx int - lastGotSort float64 - inOrder = true - errorMsg = "completions out of order" - ) - for _, w := range want { - var found bool - for i, g := range got { - if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind { - matchedIdxs = append(matchedIdxs, i) - found = true - - if i < lastGotIdx { - inOrder = false - } - lastGotIdx = i - - sort, _ := strconv.ParseFloat(g.SortText, 64) - if strictScores && len(matchedIdxs) > 1 && sort <= lastGotSort { - inOrder = false - errorMsg = "candidate scores not strictly decreasing" - } - lastGotSort = sort - - break - } - } - if !found { - return summarizeCompletionItems(-1, []protocol.CompletionItem{w}, got, "didn't find expected completion") - } - } - - sort.Ints(matchedIdxs) - matched := make([]protocol.CompletionItem, 0, len(matchedIdxs)) - for _, idx := range matchedIdxs { - matched = append(matched, got[idx]) - } - - if !inOrder { - return summarizeCompletionItems(-1, want, matched, errorMsg) - } - - return "" -} - -func DiffSnippets(want string, got *protocol.CompletionItem) string { - if want == "" { - if got != nil { - return fmt.Sprintf("expected no snippet but got %s", got.TextEdit.NewText) - } - } else { - if got == nil { - return fmt.Sprintf("couldn't find completion matching %q", want) - } - if want != got.TextEdit.NewText { - return fmt.Sprintf("expected snippet %q, got %q", want, got.TextEdit.NewText) - } - } - return "" -} - -func FindItem(list []protocol.CompletionItem, want source.CompletionItem) *protocol.CompletionItem { - for _, item := range list { - if item.Label == want.Label { - return &item - } - } - return nil -} - -// DiffCompletionItems prints the diff between expected and actual completion -// test results. -func DiffCompletionItems(want, got []protocol.CompletionItem) string { - if len(got) != len(want) { - return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want)) - } - for i, w := range want { - g := got[i] - if w.Label != g.Label { - return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label) - } - if w.Detail != g.Detail { - return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail) - } - if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") { - if w.Documentation != g.Documentation { - return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation) - } - } - if w.Kind != g.Kind { - return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind) - } - } - return "" -} - -func summarizeCompletionItems(i int, want, got []protocol.CompletionItem, reason string, args ...interface{}) string { - msg := &bytes.Buffer{} - fmt.Fprint(msg, "completion failed") - if i >= 0 { - fmt.Fprintf(msg, " at %d", i) - } - fmt.Fprint(msg, " because of ") - fmt.Fprintf(msg, reason, args...) - fmt.Fprint(msg, ":\nexpected:\n") - for _, d := range want { - fmt.Fprintf(msg, " %v\n", d) - } - fmt.Fprintf(msg, "got:\n") - for _, d := range got { - fmt.Fprintf(msg, " %v\n", d) - } - return msg.String() -} diff --git a/internal/lsp/tests/diagnostics.go b/internal/lsp/tests/diagnostics.go deleted file mode 100644 index 192cde40f5..0000000000 --- a/internal/lsp/tests/diagnostics.go +++ /dev/null @@ -1,66 +0,0 @@ -package tests - -import ( - "bytes" - "fmt" - "strings" - - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/source" - "golang.org/x/tools/internal/span" -) - -// DiffDiagnostics prints the diff between expected and actual diagnostics test -// results. -func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string { - source.SortDiagnostics(want) - source.SortDiagnostics(got) - - if len(got) != len(want) { - return summarizeDiagnostics(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want)) - } - for i, w := range want { - g := got[i] - if w.Message != g.Message { - return summarizeDiagnostics(i, uri, want, got, "incorrect Message got %v want %v", g.Message, w.Message) - } - if w.Severity != g.Severity { - return summarizeDiagnostics(i, uri, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity) - } - if w.Source != g.Source { - return summarizeDiagnostics(i, uri, want, got, "incorrect Source got %v want %v", g.Source, w.Source) - } - // Don't check the range on the badimport test. - if strings.Contains(uri.Filename(), "badimport") { - continue - } - if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 { - return summarizeDiagnostics(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start) - } - if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range. - if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 { - return summarizeDiagnostics(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End) - } - } - } - return "" -} - -func summarizeDiagnostics(i int, uri span.URI, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string { - msg := &bytes.Buffer{} - fmt.Fprint(msg, "diagnostics failed") - if i >= 0 { - fmt.Fprintf(msg, " at %d", i) - } - fmt.Fprint(msg, " because of ") - fmt.Fprintf(msg, reason, args...) - fmt.Fprint(msg, ":\nexpected:\n") - for _, d := range want { - fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message) - } - fmt.Fprintf(msg, "got:\n") - for _, d := range got { - fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message) - } - return msg.String() -} diff --git a/internal/lsp/tests/links.go b/internal/lsp/tests/links.go deleted file mode 100644 index 07fc3ef17f..0000000000 --- a/internal/lsp/tests/links.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tests - -import ( - "fmt" - "go/token" - - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/span" -) - -// DiffLinks takes the links we got and checks if they are located within the source or a Note. -// If the link is within a Note, the link is removed. -// Returns an diff comment if there are differences and empty string if no diffs -func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string { - var notePositions []token.Position - links := make(map[span.Span]string, len(wantLinks)) - for _, link := range wantLinks { - links[link.Src] = link.Target - notePositions = append(notePositions, link.NotePosition) - } - for _, link := range gotLinks { - spn, err := mapper.RangeSpan(link.Range) - if err != nil { - return fmt.Sprintf("%v", err) - } - linkInNote := false - for _, notePosition := range notePositions { - // Drop the links found inside expectation notes arguments as this links are not collected by expect package - if notePosition.Line == spn.Start().Line() && - notePosition.Column <= spn.Start().Column() { - delete(links, spn) - linkInNote = true - } - } - if linkInNote { - continue - } - if target, ok := links[spn]; ok { - delete(links, spn) - if target != link.Target { - return fmt.Sprintf("for %v want %v, got %v\n", spn, link.Target, target) - } - } else { - return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target) - } - } - for spn, target := range links { - return fmt.Sprintf("missing link %v:%v\n", spn, target) - } - return "" -} diff --git a/internal/lsp/tests/symbol.go b/internal/lsp/tests/symbol.go deleted file mode 100644 index 24ee2baeaf..0000000000 --- a/internal/lsp/tests/symbol.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tests - -import ( - "bytes" - "fmt" - "sort" - "testing" - - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/span" -) - -// DiffSymbols prints the diff between expected and actual symbols test results. -func DiffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string { - sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name }) - sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name }) - if len(got) != len(want) { - return summarizeSymbols(t, -1, want, got, "different lengths got %v want %v", len(got), len(want)) - } - for i, w := range want { - g := got[i] - if w.Name != g.Name { - return summarizeSymbols(t, i, want, got, "incorrect name got %v want %v", g.Name, w.Name) - } - if w.Kind != g.Kind { - return summarizeSymbols(t, i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind) - } - if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 { - return summarizeSymbols(t, i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange) - } - if msg := DiffSymbols(t, uri, w.Children, g.Children); msg != "" { - return fmt.Sprintf("children of %s: %s", w.Name, msg) - } - } - return "" -} - -func summarizeSymbols(t *testing.T, i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string { - msg := &bytes.Buffer{} - fmt.Fprint(msg, "document symbols failed") - if i >= 0 { - fmt.Fprintf(msg, " at %d", i) - } - fmt.Fprint(msg, " because of ") - fmt.Fprintf(msg, reason, args...) - fmt.Fprint(msg, ":\nexpected:\n") - for _, s := range want { - fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange) - } - fmt.Fprintf(msg, "got:\n") - for _, s := range got { - fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange) - } - return msg.String() -} diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go index 23dcf874e5..bf1617779a 100644 --- a/internal/lsp/tests/util.go +++ b/internal/lsp/tests/util.go @@ -1,13 +1,164 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package tests import ( + "bytes" "fmt" + "go/token" + "sort" + "strconv" "strings" + "testing" "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" ) +// DiffLinks takes the links we got and checks if they are located within the source or a Note. +// If the link is within a Note, the link is removed. +// Returns an diff comment if there are differences and empty string if no diffs +func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string { + var notePositions []token.Position + links := make(map[span.Span]string, len(wantLinks)) + for _, link := range wantLinks { + links[link.Src] = link.Target + notePositions = append(notePositions, link.NotePosition) + } + for _, link := range gotLinks { + spn, err := mapper.RangeSpan(link.Range) + if err != nil { + return fmt.Sprintf("%v", err) + } + linkInNote := false + for _, notePosition := range notePositions { + // Drop the links found inside expectation notes arguments as this links are not collected by expect package + if notePosition.Line == spn.Start().Line() && + notePosition.Column <= spn.Start().Column() { + delete(links, spn) + linkInNote = true + } + } + if linkInNote { + continue + } + if target, ok := links[spn]; ok { + delete(links, spn) + if target != link.Target { + return fmt.Sprintf("for %v want %v, got %v\n", spn, link.Target, target) + } + } else { + return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target) + } + } + for spn, target := range links { + return fmt.Sprintf("missing link %v:%v\n", spn, target) + } + return "" +} + +// DiffSymbols prints the diff between expected and actual symbols test results. +func DiffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string { + sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name }) + sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name }) + if len(got) != len(want) { + return summarizeSymbols(t, -1, want, got, "different lengths got %v want %v", len(got), len(want)) + } + for i, w := range want { + g := got[i] + if w.Name != g.Name { + return summarizeSymbols(t, i, want, got, "incorrect name got %v want %v", g.Name, w.Name) + } + if w.Kind != g.Kind { + return summarizeSymbols(t, i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind) + } + if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 { + return summarizeSymbols(t, i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange) + } + if msg := DiffSymbols(t, uri, w.Children, g.Children); msg != "" { + return fmt.Sprintf("children of %s: %s", w.Name, msg) + } + } + return "" +} + +func summarizeSymbols(t *testing.T, i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string { + msg := &bytes.Buffer{} + fmt.Fprint(msg, "document symbols failed") + if i >= 0 { + fmt.Fprintf(msg, " at %d", i) + } + fmt.Fprint(msg, " because of ") + fmt.Fprintf(msg, reason, args...) + fmt.Fprint(msg, ":\nexpected:\n") + for _, s := range want { + fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange) + } + fmt.Fprintf(msg, "got:\n") + for _, s := range got { + fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange) + } + return msg.String() +} + +// DiffDiagnostics prints the diff between expected and actual diagnostics test +// results. +func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string { + source.SortDiagnostics(want) + source.SortDiagnostics(got) + + if len(got) != len(want) { + return summarizeDiagnostics(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want)) + } + for i, w := range want { + g := got[i] + if w.Message != g.Message { + return summarizeDiagnostics(i, uri, want, got, "incorrect Message got %v want %v", g.Message, w.Message) + } + if w.Severity != g.Severity { + return summarizeDiagnostics(i, uri, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity) + } + if w.Source != g.Source { + return summarizeDiagnostics(i, uri, want, got, "incorrect Source got %v want %v", g.Source, w.Source) + } + // Don't check the range on the badimport test. + if strings.Contains(uri.Filename(), "badimport") { + continue + } + if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 { + return summarizeDiagnostics(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start) + } + if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range. + if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 { + return summarizeDiagnostics(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End) + } + } + } + return "" +} + +func summarizeDiagnostics(i int, uri span.URI, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string { + msg := &bytes.Buffer{} + fmt.Fprint(msg, "diagnostics failed") + if i >= 0 { + fmt.Fprintf(msg, " at %d", i) + } + fmt.Fprint(msg, " because of ") + fmt.Fprintf(msg, reason, args...) + fmt.Fprint(msg, ":\nexpected:\n") + for _, d := range want { + fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message) + } + fmt.Fprintf(msg, "got:\n") + for _, d := range got { + fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message) + } + return msg.String() +} + func DiffSignatures(spn span.Span, want, got *protocol.SignatureHelp) string { decorate := func(f string, args ...interface{}) string { return fmt.Sprintf("Invalid signature at %s: %s", spn, fmt.Sprintf(f, args...)) @@ -42,3 +193,186 @@ func DiffSignatures(spn span.Span, want, got *protocol.SignatureHelp) string { return "" } + +func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem { + var result []protocol.CompletionItem + for _, item := range items { + result = append(result, ToProtocolCompletionItem(item)) + } + return result +} + +func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem { + pItem := protocol.CompletionItem{ + Label: item.Label, + Kind: item.Kind, + Detail: item.Detail, + Documentation: item.Documentation, + InsertText: item.InsertText, + TextEdit: &protocol.TextEdit{ + NewText: item.Snippet(), + }, + // Negate score so best score has lowest sort text like real API. + SortText: fmt.Sprint(-item.Score), + } + if pItem.InsertText == "" { + pItem.InsertText = pItem.Label + } + return pItem +} + +func FilterBuiltins(src span.Span, items []protocol.CompletionItem) []protocol.CompletionItem { + var ( + got []protocol.CompletionItem + wantBuiltins = strings.Contains(string(src.URI()), "builtins") + wantKeywords = strings.Contains(string(src.URI()), "keywords") + ) + for _, item := range items { + if !wantBuiltins && isBuiltin(item.Label, item.Detail, item.Kind) { + continue + } + + if !wantKeywords && token.Lookup(item.Label).IsKeyword() { + continue + } + + got = append(got, item) + } + return got +} + +func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool { + if detail == "" && kind == protocol.ClassCompletion { + return true + } + // Remaining builtin constants, variables, interfaces, and functions. + trimmed := label + if i := strings.Index(trimmed, "("); i >= 0 { + trimmed = trimmed[:i] + } + switch trimmed { + case "append", "cap", "close", "complex", "copy", "delete", + "error", "false", "imag", "iota", "len", "make", "new", + "nil", "panic", "print", "println", "real", "recover", "true": + return true + } + return false +} + +func CheckCompletionOrder(want, got []protocol.CompletionItem, strictScores bool) string { + var ( + matchedIdxs []int + lastGotIdx int + lastGotSort float64 + inOrder = true + errorMsg = "completions out of order" + ) + for _, w := range want { + var found bool + for i, g := range got { + if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind { + matchedIdxs = append(matchedIdxs, i) + found = true + + if i < lastGotIdx { + inOrder = false + } + lastGotIdx = i + + sort, _ := strconv.ParseFloat(g.SortText, 64) + if strictScores && len(matchedIdxs) > 1 && sort <= lastGotSort { + inOrder = false + errorMsg = "candidate scores not strictly decreasing" + } + lastGotSort = sort + + break + } + } + if !found { + return summarizeCompletionItems(-1, []protocol.CompletionItem{w}, got, "didn't find expected completion") + } + } + + sort.Ints(matchedIdxs) + matched := make([]protocol.CompletionItem, 0, len(matchedIdxs)) + for _, idx := range matchedIdxs { + matched = append(matched, got[idx]) + } + + if !inOrder { + return summarizeCompletionItems(-1, want, matched, errorMsg) + } + + return "" +} + +func DiffSnippets(want string, got *protocol.CompletionItem) string { + if want == "" { + if got != nil { + return fmt.Sprintf("expected no snippet but got %s", got.TextEdit.NewText) + } + } else { + if got == nil { + return fmt.Sprintf("couldn't find completion matching %q", want) + } + if want != got.TextEdit.NewText { + return fmt.Sprintf("expected snippet %q, got %q", want, got.TextEdit.NewText) + } + } + return "" +} + +func FindItem(list []protocol.CompletionItem, want source.CompletionItem) *protocol.CompletionItem { + for _, item := range list { + if item.Label == want.Label { + return &item + } + } + return nil +} + +// DiffCompletionItems prints the diff between expected and actual completion +// test results. +func DiffCompletionItems(want, got []protocol.CompletionItem) string { + if len(got) != len(want) { + return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want)) + } + for i, w := range want { + g := got[i] + if w.Label != g.Label { + return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label) + } + if w.Detail != g.Detail { + return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail) + } + if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") { + if w.Documentation != g.Documentation { + return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation) + } + } + if w.Kind != g.Kind { + return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind) + } + } + return "" +} + +func summarizeCompletionItems(i int, want, got []protocol.CompletionItem, reason string, args ...interface{}) string { + msg := &bytes.Buffer{} + fmt.Fprint(msg, "completion failed") + if i >= 0 { + fmt.Fprintf(msg, " at %d", i) + } + fmt.Fprint(msg, " because of ") + fmt.Fprintf(msg, reason, args...) + fmt.Fprint(msg, ":\nexpected:\n") + for _, d := range want { + fmt.Fprintf(msg, " %v\n", d) + } + fmt.Fprintf(msg, "got:\n") + for _, d := range got { + fmt.Fprintf(msg, " %v\n", d) + } + return msg.String() +}