mirror of
https://github.com/golang/go
synced 2024-11-18 14:54:40 -07:00
internal/lsp: reorganize completion tests
Our completion tests check for a lot of different behaviors. It may be easier to develop if we have separate tests for things like deep completion and completion snippets. Change-Id: I7f4b0c0e52670f2a6c00247199933fd1ffa0096f Reviewed-on: https://go-review.googlesource.com/c/tools/+/196021 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
3ac2a5bbd9
commit
c006dc79eb
@ -33,7 +33,27 @@ func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Con
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
|
||||
func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
|
||||
//TODO: add command line completions tests when it works
|
||||
}
|
||||
|
||||
func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
|
||||
//TODO: add command line completions tests when it works
|
||||
}
|
||||
|
||||
func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
|
||||
//TODO: add command line completions tests when it works
|
||||
}
|
||||
|
||||
func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
|
||||
//TODO: add command line completions tests when it works
|
||||
}
|
||||
|
||||
func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
|
||||
//TODO: add command line completions tests when it works
|
||||
}
|
||||
|
||||
func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
|
||||
//TODO: add command line completions tests when it works
|
||||
}
|
||||
|
||||
|
@ -47,11 +47,11 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
|
||||
// When using deep completions/fuzzy matching, report results as incomplete so
|
||||
// client fetches updated completions after every key stroke.
|
||||
IsIncomplete: options.Completion.Deep,
|
||||
Items: s.toProtocolCompletionItems(candidates, rng, options),
|
||||
Items: toProtocolCompletionItems(candidates, rng, options),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
|
||||
func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
|
||||
var (
|
||||
items = make([]protocol.CompletionItem, 0, len(candidates))
|
||||
numDeepCompletionsSeen int
|
||||
|
151
internal/lsp/completion_test.go
Normal file
151
internal/lsp/completion_test.go
Normal file
@ -0,0 +1,151 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/lsp/tests"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Deep: false,
|
||||
FuzzyMatching: false,
|
||||
Documentation: true,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
got = tests.FilterBuiltins(got)
|
||||
}
|
||||
want := expected(t, test, items)
|
||||
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||
t.Errorf("%s: %s", src, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
|
||||
for _, placeholders := range []bool{true, false} {
|
||||
for src, expected := range data {
|
||||
list := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Placeholders: placeholders,
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
FuzzyMatching: true,
|
||||
})
|
||||
got := tests.FindItem(list, *items[expected.CompletionItem])
|
||||
want := expected.PlainSnippet
|
||||
if placeholders {
|
||||
want = expected.PlaceholderSnippet
|
||||
}
|
||||
if diff := tests.DiffSnippets(want, got); diff != "" {
|
||||
t.Errorf("%s: %v", src, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Unimported: true,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
got = tests.FilterBuiltins(got)
|
||||
}
|
||||
want := expected(t, test, items)
|
||||
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||
t.Errorf("%s: %s", src, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
Documentation: true,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
got = tests.FilterBuiltins(got)
|
||||
}
|
||||
want := expected(t, test, items)
|
||||
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||
FuzzyMatching: true,
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
got = tests.FilterBuiltins(got)
|
||||
}
|
||||
want := expected(t, test, items)
|
||||
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||
FuzzyMatching: true,
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
})
|
||||
want := expected(t, test, items)
|
||||
if msg := tests.CheckCompletionOrder(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expected(t *testing.T, test tests.Completion, items tests.CompletionItems) []protocol.CompletionItem {
|
||||
t.Helper()
|
||||
|
||||
var want []protocol.CompletionItem
|
||||
for _, pos := range test.CompletionItems {
|
||||
item := items[pos]
|
||||
want = append(want, tests.ToProtocolCompletionItem(*item))
|
||||
}
|
||||
return want
|
||||
}
|
||||
func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) []protocol.CompletionItem {
|
||||
t.Helper()
|
||||
|
||||
view := r.server.session.ViewOf(src.URI())
|
||||
original := view.Options()
|
||||
modified := original
|
||||
modified.InsertTextFormat = protocol.SnippetTextFormat
|
||||
modified.Completion = options
|
||||
view.SetOptions(modified)
|
||||
defer view.SetOptions(original)
|
||||
|
||||
list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
|
||||
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||
TextDocument: protocol.TextDocumentIdentifier{
|
||||
URI: protocol.NewURI(src.URI()),
|
||||
},
|
||||
Position: protocol.Position{
|
||||
Line: float64(src.Start().Line() - 1),
|
||||
Character: float64(src.Start().Column() - 1),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return list.Items
|
||||
}
|
@ -15,7 +15,6 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
"golang.org/x/tools/internal/lsp/cache"
|
||||
@ -51,18 +50,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
|
||||
|
||||
cache := cache.New()
|
||||
session := cache.NewSession(ctx)
|
||||
options := session.Options()
|
||||
options.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
|
||||
source.Go: {
|
||||
protocol.SourceOrganizeImports: true,
|
||||
protocol.QuickFix: true,
|
||||
},
|
||||
source.Mod: {},
|
||||
source.Sum: {},
|
||||
}
|
||||
options.HoverKind = source.SynopsisDocumentation
|
||||
// Crank this up so tests don't flake.
|
||||
options.Completion.Budget = 5 * time.Second
|
||||
options := tests.DefaultOptions()
|
||||
session.SetOptions(options)
|
||||
options.Env = data.Config.Env
|
||||
session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
|
||||
@ -111,218 +99,6 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
view := r.server.session.ViewOf(src.URI())
|
||||
original := view.Options()
|
||||
modified := original
|
||||
|
||||
// Set this as a default.
|
||||
modified.Completion.Documentation = true
|
||||
|
||||
var want []source.CompletionItem
|
||||
for _, pos := range test.CompletionItems {
|
||||
want = append(want, *items[pos])
|
||||
}
|
||||
|
||||
modified.Completion.Deep = strings.Contains(string(src.URI()), "deepcomplete")
|
||||
modified.Completion.FuzzyMatching = strings.Contains(string(src.URI()), "fuzzymatch")
|
||||
modified.Completion.Unimported = strings.Contains(string(src.URI()), "unimported")
|
||||
view.SetOptions(modified)
|
||||
|
||||
list := r.runCompletion(t, src)
|
||||
|
||||
wantBuiltins := strings.Contains(string(src.URI()), "builtins")
|
||||
var got []protocol.CompletionItem
|
||||
for _, item := range list.Items {
|
||||
if !wantBuiltins && isBuiltin(item) {
|
||||
continue
|
||||
}
|
||||
got = append(got, item)
|
||||
}
|
||||
|
||||
switch test.Type {
|
||||
case tests.CompletionFull:
|
||||
if diff := diffCompletionItems(want, got); diff != "" {
|
||||
t.Errorf("%s: %s", src, diff)
|
||||
}
|
||||
case tests.CompletionPartial:
|
||||
if msg := checkCompletionOrder(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
view.SetOptions(original)
|
||||
}
|
||||
|
||||
for _, usePlaceholders := range []bool{true, false} {
|
||||
|
||||
for src, want := range snippets {
|
||||
view := r.server.session.ViewOf(src.URI())
|
||||
original := view.Options()
|
||||
modified := original
|
||||
|
||||
modified.InsertTextFormat = protocol.SnippetTextFormat
|
||||
modified.Completion.Deep = strings.Contains(string(src.URI()), "deepcomplete")
|
||||
modified.Completion.FuzzyMatching = strings.Contains(string(src.URI()), "fuzzymatch")
|
||||
modified.Completion.Unimported = strings.Contains(string(src.URI()), "unimported")
|
||||
modified.Completion.Placeholders = usePlaceholders
|
||||
view.SetOptions(modified)
|
||||
|
||||
list := r.runCompletion(t, src)
|
||||
|
||||
wantItem := items[want.CompletionItem]
|
||||
var got *protocol.CompletionItem
|
||||
for _, item := range list.Items {
|
||||
if item.Label == wantItem.Label {
|
||||
got = &item
|
||||
break
|
||||
}
|
||||
}
|
||||
var expected string
|
||||
if usePlaceholders {
|
||||
expected = want.PlaceholderSnippet
|
||||
} else {
|
||||
expected = want.PlainSnippet
|
||||
}
|
||||
|
||||
if expected == "" {
|
||||
if got != nil {
|
||||
t.Fatalf("%s:%d: expected no snippet but got %q", src.URI(), src.Start().Line(), got.TextEdit.NewText)
|
||||
}
|
||||
} else {
|
||||
if got == nil {
|
||||
t.Fatalf("%s:%d: couldn't find completion matching %q", src.URI(), src.Start().Line(), wantItem.Label)
|
||||
}
|
||||
|
||||
if expected != got.TextEdit.NewText {
|
||||
t.Errorf("%s: expected snippet %q, got %q", src, expected, got.TextEdit.NewText)
|
||||
}
|
||||
}
|
||||
view.SetOptions(original)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) runCompletion(t *testing.T, src span.Span) *protocol.CompletionList {
|
||||
t.Helper()
|
||||
list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
|
||||
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||
TextDocument: protocol.TextDocumentIdentifier{
|
||||
URI: protocol.NewURI(src.URI()),
|
||||
},
|
||||
Position: protocol.Position{
|
||||
Line: float64(src.Start().Line() - 1),
|
||||
Character: float64(src.Start().Column() - 1),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func isBuiltin(item protocol.CompletionItem) bool {
|
||||
// If a type has no detail, it is a builtin type.
|
||||
if item.Detail == "" && item.Kind == protocol.TypeParameterCompletion {
|
||||
return true
|
||||
}
|
||||
// Remaining builtin constants, variables, interfaces, and functions.
|
||||
trimmed := item.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
|
||||
}
|
||||
|
||||
// diffCompletionItems prints the diff between expected and actual completion
|
||||
// test results.
|
||||
func diffCompletionItems(want []source.CompletionItem, 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 wkind := toProtocolCompletionItemKind(w.Kind); wkind != g.Kind {
|
||||
return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, wkind)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkCompletionOrder(want []source.CompletionItem, got []protocol.CompletionItem) string {
|
||||
var (
|
||||
matchedIdxs []int
|
||||
lastGotIdx int
|
||||
inOrder = true
|
||||
)
|
||||
for _, w := range want {
|
||||
var found bool
|
||||
for i, g := range got {
|
||||
if w.Label == g.Label && w.Detail == g.Detail && toProtocolCompletionItemKind(w.Kind) == g.Kind {
|
||||
matchedIdxs = append(matchedIdxs, i)
|
||||
found = true
|
||||
if i < lastGotIdx {
|
||||
inOrder = false
|
||||
}
|
||||
lastGotIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return summarizeCompletionItems(-1, []source.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, "completions out of order")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func summarizeCompletionItems(i int, want []source.CompletionItem, 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()
|
||||
}
|
||||
|
||||
func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
|
||||
for _, spn := range data {
|
||||
uri := spn.URI()
|
||||
@ -909,7 +685,7 @@ func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []protocol.Documen
|
||||
return ""
|
||||
}
|
||||
|
||||
func summarizeSymbols(t *testing.T, i int, want []protocol.DocumentSymbol, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
|
||||
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 {
|
||||
|
@ -49,7 +49,7 @@ func testSource(t *testing.T, exporter packagestest.Exporter) {
|
||||
|
||||
cache := cache.New()
|
||||
session := cache.NewSession(ctx)
|
||||
options := session.Options()
|
||||
options := tests.DefaultOptions()
|
||||
options.Env = data.Config.Env
|
||||
r := &runner{
|
||||
view: session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options),
|
||||
@ -86,245 +86,194 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
|
||||
ctx := r.ctx
|
||||
func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
var want []source.CompletionItem
|
||||
var want []protocol.CompletionItem
|
||||
for _, pos := range test.CompletionItems {
|
||||
want = append(want, *items[pos])
|
||||
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||
}
|
||||
f, err := r.view.GetFile(ctx, src.URI())
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", src, err)
|
||||
}
|
||||
deepComplete := strings.Contains(string(src.URI()), "deepcomplete")
|
||||
fuzzyMatch := strings.Contains(string(src.URI()), "fuzzymatch")
|
||||
unimported := strings.Contains(string(src.URI()), "unimported")
|
||||
list, surrounding, err := source.Completion(ctx, r.view, f.(source.GoFile), protocol.Position{
|
||||
Line: float64(src.Start().Line() - 1),
|
||||
Character: float64(src.Start().Column() - 1),
|
||||
}, source.CompletionOptions{
|
||||
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Documentation: true,
|
||||
Deep: deepComplete,
|
||||
FuzzyMatching: fuzzyMatch,
|
||||
Unimported: unimported,
|
||||
// Crank this up so tests don't flake.
|
||||
Budget: 5 * time.Second,
|
||||
FuzzyMatching: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", src, err)
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
list = tests.FilterBuiltins(list)
|
||||
}
|
||||
var (
|
||||
prefix string
|
||||
fuzzyMatcher *fuzzy.Matcher
|
||||
)
|
||||
if surrounding != nil {
|
||||
prefix = strings.ToLower(surrounding.Prefix())
|
||||
if deepComplete && prefix != "" {
|
||||
fuzzyMatcher = fuzzy.NewMatcher(surrounding.Prefix(), fuzzy.Symbol)
|
||||
}
|
||||
}
|
||||
wantBuiltins := strings.Contains(string(src.URI()), "builtins")
|
||||
var got []source.CompletionItem
|
||||
var got []protocol.CompletionItem
|
||||
for _, item := range list {
|
||||
if !wantBuiltins && isBuiltin(item) {
|
||||
if !strings.HasPrefix(strings.ToLower(item.Label), prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If deep completion is enabled, we need to use the fuzzy matcher to match
|
||||
// the code's behavior.
|
||||
if deepComplete {
|
||||
if fuzzyMatcher != nil && fuzzyMatcher.Score(item.Label) < 0 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// We let the client do fuzzy matching, so we return all possible candidates.
|
||||
// To simplify testing, filter results with prefixes that don't match exactly.
|
||||
if !strings.HasPrefix(strings.ToLower(item.Label), prefix) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
got = append(got, item)
|
||||
}
|
||||
switch test.Type {
|
||||
case tests.CompletionFull:
|
||||
if diff := diffCompletionItems(want, got); diff != "" {
|
||||
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||
t.Errorf("%s: %s", src, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
|
||||
for _, placeholders := range []bool{true, false} {
|
||||
for src, expected := range data {
|
||||
_, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Placeholders: placeholders,
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
})
|
||||
got := tests.FindItem(list, *items[expected.CompletionItem])
|
||||
want := expected.PlainSnippet
|
||||
if placeholders {
|
||||
want = expected.PlaceholderSnippet
|
||||
}
|
||||
if diff := tests.DiffSnippets(want, got); diff != "" {
|
||||
t.Errorf("%s: %s", src, diff)
|
||||
}
|
||||
case tests.CompletionPartial:
|
||||
if msg := checkCompletionOrder(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, usePlaceholders := range []bool{true, false} {
|
||||
for src, want := range snippets {
|
||||
f, err := r.view.GetFile(ctx, src.URI())
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", src, err)
|
||||
}
|
||||
|
||||
list, _, err := source.Completion(ctx, r.view, f.(source.GoFile), protocol.Position{
|
||||
Line: float64(src.Start().Line() - 1),
|
||||
Character: float64(src.Start().Column() - 1),
|
||||
}, source.CompletionOptions{
|
||||
Documentation: true,
|
||||
Deep: strings.Contains(string(src.URI()), "deepcomplete"),
|
||||
FuzzyMatching: strings.Contains(string(src.URI()), "fuzzymatch"),
|
||||
Placeholders: usePlaceholders,
|
||||
// Crank this up so tests don't flake.
|
||||
Budget: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", src, err)
|
||||
}
|
||||
wantItem := items[want.CompletionItem]
|
||||
var got *source.CompletionItem
|
||||
for _, item := range list {
|
||||
if item.Label == wantItem.Label {
|
||||
got = &item
|
||||
break
|
||||
}
|
||||
}
|
||||
expected := want.PlainSnippet
|
||||
if usePlaceholders {
|
||||
expected = want.PlaceholderSnippet
|
||||
}
|
||||
if expected == "" {
|
||||
if got != nil {
|
||||
t.Fatalf("%s:%d: expected no matching snippet", src.URI(), src.Start().Line())
|
||||
}
|
||||
} else {
|
||||
if got == nil {
|
||||
t.Fatalf("%s:%d: couldn't find completion matching %q", src.URI(), src.Start().Line(), wantItem.Label)
|
||||
}
|
||||
actual := got.Snippet()
|
||||
if expected != actual {
|
||||
t.Errorf("%s: expected placeholder snippet %q, got %q", src, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isBuiltin(item source.CompletionItem) bool {
|
||||
// If a type has no detail, it is a builtin type.
|
||||
if item.Detail == "" && item.Kind == source.TypeCompletionItem {
|
||||
return true
|
||||
func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
var want []protocol.CompletionItem
|
||||
for _, pos := range test.CompletionItems {
|
||||
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||
}
|
||||
_, got := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Unimported: true,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
got = tests.FilterBuiltins(got)
|
||||
}
|
||||
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||
t.Errorf("%s: %s", src, diff)
|
||||
}
|
||||
}
|
||||
// Remaining builtin constants, variables, interfaces, and functions.
|
||||
trimmed := item.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
|
||||
}
|
||||
|
||||
// diffCompletionItems prints the diff between expected and actual completion
|
||||
// test results.
|
||||
func diffCompletionItems(want []source.CompletionItem, got []source.CompletionItem) string {
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].Score > got[j].Score
|
||||
})
|
||||
|
||||
// duplicate the lsp/completion logic to limit deep candidates to keep expected
|
||||
// list short
|
||||
var idx, seenDeepCompletions int
|
||||
for _, item := range got {
|
||||
if item.Depth > 0 {
|
||||
if seenDeepCompletions >= 3 {
|
||||
func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
var want []protocol.CompletionItem
|
||||
for _, pos := range test.CompletionItems {
|
||||
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||
}
|
||||
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
Documentation: true,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
list = tests.FilterBuiltins(list)
|
||||
}
|
||||
fuzzyMatcher := fuzzy.NewMatcher(prefix, fuzzy.Symbol)
|
||||
var got []protocol.CompletionItem
|
||||
for _, item := range list {
|
||||
if fuzzyMatcher.Score(item.Label) < 0 {
|
||||
continue
|
||||
}
|
||||
seenDeepCompletions++
|
||||
got = append(got, item)
|
||||
}
|
||||
got[idx] = item
|
||||
idx++
|
||||
}
|
||||
got = got[:idx]
|
||||
|
||||
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)
|
||||
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkCompletionOrder(want []source.CompletionItem, got []source.CompletionItem) string {
|
||||
var (
|
||||
matchedIdxs []int
|
||||
lastGotIdx int
|
||||
inOrder = true
|
||||
)
|
||||
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
|
||||
break
|
||||
func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
var want []protocol.CompletionItem
|
||||
for _, pos := range test.CompletionItems {
|
||||
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||
}
|
||||
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||
FuzzyMatching: true,
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
list = tests.FilterBuiltins(list)
|
||||
}
|
||||
var fuzzyMatcher *fuzzy.Matcher
|
||||
if prefix != "" {
|
||||
fuzzyMatcher = fuzzy.NewMatcher(prefix, fuzzy.Symbol)
|
||||
}
|
||||
var got []protocol.CompletionItem
|
||||
for _, item := range list {
|
||||
if fuzzyMatcher != nil && fuzzyMatcher.Score(item.Label) < 0 {
|
||||
continue
|
||||
}
|
||||
got = append(got, item)
|
||||
}
|
||||
if !found {
|
||||
return summarizeCompletionItems(-1, []source.CompletionItem{w}, got, "didn't find expected completion")
|
||||
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Ints(matchedIdxs)
|
||||
matched := make([]source.CompletionItem, 0, len(matchedIdxs))
|
||||
for _, idx := range matchedIdxs {
|
||||
matched = append(matched, got[idx])
|
||||
}
|
||||
|
||||
if !inOrder {
|
||||
return summarizeCompletionItems(-1, want, matched, "completions out of order")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func summarizeCompletionItems(i int, want []source.CompletionItem, got []source.CompletionItem, reason string, args ...interface{}) string {
|
||||
msg := &bytes.Buffer{}
|
||||
fmt.Fprint(msg, "completion failed")
|
||||
if i >= 0 {
|
||||
fmt.Fprintf(msg, " at %d", i)
|
||||
func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
|
||||
for src, test := range data {
|
||||
var want []protocol.CompletionItem
|
||||
for _, pos := range test.CompletionItems {
|
||||
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||
}
|
||||
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||
FuzzyMatching: true,
|
||||
Deep: true,
|
||||
Budget: 5 * time.Second,
|
||||
})
|
||||
if !strings.Contains(string(src.URI()), "builtins") {
|
||||
list = tests.FilterBuiltins(list)
|
||||
}
|
||||
fuzzyMatcher := fuzzy.NewMatcher(prefix, fuzzy.Symbol)
|
||||
var got []protocol.CompletionItem
|
||||
for _, item := range list {
|
||||
if fuzzyMatcher.Score(item.Label) < 0 {
|
||||
continue
|
||||
}
|
||||
got = append(got, item)
|
||||
}
|
||||
if msg := tests.CheckCompletionOrder(want, got); msg != "" {
|
||||
t.Errorf("%s: %s", src, msg)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) (string, []protocol.CompletionItem) {
|
||||
f, err := r.view.GetFile(r.ctx, src.URI())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Fprintf(msg, "got:\n")
|
||||
for _, d := range got {
|
||||
fmt.Fprintf(msg, " %v\n", d)
|
||||
list, surrounding, err := source.Completion(r.ctx, r.view, f.(source.GoFile), protocol.Position{
|
||||
Line: float64(src.Start().Line() - 1),
|
||||
Character: float64(src.Start().Column() - 1),
|
||||
}, options)
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", src, err)
|
||||
}
|
||||
return msg.String()
|
||||
var prefix string
|
||||
if surrounding != nil {
|
||||
prefix = strings.ToLower(surrounding.Prefix())
|
||||
}
|
||||
// TODO(rstambler): In testing this out, I noticed that scores are equal,
|
||||
// even when they shouldn't be. This needs more investigation.
|
||||
sort.SliceStable(list, func(i, j int) bool {
|
||||
return list[i].Score > list[j].Score
|
||||
})
|
||||
var numDeepCompletionsSeen int
|
||||
var items []source.CompletionItem
|
||||
// Apply deep completion filtering.
|
||||
for _, item := range list {
|
||||
if item.Depth > 0 {
|
||||
if !options.Deep {
|
||||
continue
|
||||
}
|
||||
if numDeepCompletionsSeen >= source.MaxDeepCompletions {
|
||||
continue
|
||||
}
|
||||
numDeepCompletionsSeen++
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return prefix, tests.ToProtocolCompletionItems(items)
|
||||
}
|
||||
|
||||
func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package deepcomplete
|
||||
package deep
|
||||
|
||||
import "context"
|
||||
|
||||
@ -18,7 +18,7 @@ func wantsDeepB(deepB) {}
|
||||
func _() {
|
||||
var a deepA //@item(deepAVar, "a", "deepA", "var")
|
||||
a.b //@item(deepABField, "a.b", "deepB", "field")
|
||||
wantsDeepB(a) //@complete(")", deepABField, deepAVar)
|
||||
wantsDeepB(a) //@deep(")", deepABField, deepAVar)
|
||||
|
||||
deepA{a} //@snippet("}", deepABField, "a.b", "a.b")
|
||||
}
|
||||
@ -29,7 +29,7 @@ func _() {
|
||||
context.Background() //@item(ctxBackground, "context.Background", "func() context.Context", "func", "Background returns a non-nil, empty Context.")
|
||||
context.TODO() //@item(ctxTODO, "context.TODO", "func() context.Context", "func", "TODO returns a non-nil, empty Context.")
|
||||
|
||||
wantsContext(c) //@completePartial(")", ctxBackground, ctxTODO)
|
||||
wantsContext(c) //@rank(")", ctxBackground, ctxTODO)
|
||||
}
|
||||
|
||||
func _() {
|
||||
@ -39,7 +39,7 @@ func _() {
|
||||
}
|
||||
var circle deepCircle //@item(deepCircle, "circle", "deepCircle", "var")
|
||||
circle.deepCircle //@item(deepCircleField, "circle.deepCircle", "*deepCircle", "field", "deepCircle is circular.")
|
||||
var _ deepCircle = circ //@complete(" //", deepCircle, deepCircleField)
|
||||
var _ deepCircle = circ //@deep(" //", deepCircle, deepCircleField)
|
||||
}
|
||||
|
||||
func _() {
|
||||
@ -57,7 +57,7 @@ func _() {
|
||||
var a deepEmbedA //@item(deepEmbedA, "a", "deepEmbedA", "var")
|
||||
a.deepEmbedB //@item(deepEmbedB, "a.deepEmbedB", "deepEmbedB", "field")
|
||||
a.deepEmbedC //@item(deepEmbedC, "a.deepEmbedC", "deepEmbedC", "field")
|
||||
wantsC(a) //@complete(")", deepEmbedC, deepEmbedA, deepEmbedB)
|
||||
wantsC(a) //@deep(")", deepEmbedC, deepEmbedA, deepEmbedB)
|
||||
}
|
||||
|
||||
func _() {
|
||||
@ -67,7 +67,7 @@ func _() {
|
||||
}
|
||||
|
||||
nested{
|
||||
a: 123, //@complete(" //", deepNestedField)
|
||||
a: 123, //@deep(" //", deepNestedField)
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,5 +86,5 @@ func _() {
|
||||
|
||||
// "a.d" should be ranked above the deeper "a.b.c"
|
||||
var i int
|
||||
i = a //@complete(" //", deepAD, deepABC, deepA, deepAB)
|
||||
i = a //@deep(" //", deepAD, deepABC, deepA, deepAB)
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuzzymatch
|
||||
package fuzzy
|
||||
|
||||
func _() {
|
||||
var a struct {
|
||||
@ -13,13 +13,13 @@ func _() {
|
||||
a.fabar //@item(fuzzFabarField, "a.fabar", "int", "field")
|
||||
a.fooBar //@item(fuzzFooBarField, "a.fooBar", "string", "field")
|
||||
|
||||
afa //@complete(" //", fuzzFabarField, fuzzFooBarField)
|
||||
afb //@complete(" //", fuzzFooBarField, fuzzFabarField)
|
||||
afa //@fuzzy(" //", fuzzFabarField, fuzzFooBarField)
|
||||
afb //@fuzzy(" //", fuzzFooBarField, fuzzFabarField)
|
||||
|
||||
fab //@complete(" //", fuzzFabarField)
|
||||
fab //@fuzzy(" //", fuzzFabarField)
|
||||
|
||||
var myString string
|
||||
myString = af //@complete(" //", fuzzFooBarField, fuzzFabarField)
|
||||
myString = af //@fuzzy(" //", fuzzFooBarField, fuzzFabarField)
|
||||
|
||||
var b struct {
|
||||
c struct {
|
||||
@ -40,9 +40,9 @@ func _() {
|
||||
b.c.d.e.abc //@item(fuzzABCstring, "b.c.d.e.abc", "string", "field")
|
||||
|
||||
// in depth order by default
|
||||
abc //@complete(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat)
|
||||
abc //@fuzzy(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat)
|
||||
|
||||
// deep candidate that matches expected type should still ranked first
|
||||
var s string
|
||||
s = abc //@complete(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool)
|
||||
s = abc //@fuzzy(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool)
|
||||
}
|
@ -103,7 +103,7 @@ func _() {
|
||||
}
|
||||
|
||||
func _() {
|
||||
"func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
|
||||
_ = "func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
|
||||
|
||||
sort.Slice(nil, f) //@snippet(")", litFunc, "func(i, j int) bool {$0\\}", "func(i, j int) bool {$0\\}")
|
||||
|
||||
|
@ -91,7 +91,7 @@ func main() {
|
||||
marker := strings.ReplaceAll(path, "/", "slash")
|
||||
markers = append(markers, marker)
|
||||
}
|
||||
outf(" //@complete(\"\", %s)\n", strings.Join(markers, ", "))
|
||||
outf(" //@unimported(\"\", %s)\n", strings.Join(markers, ", "))
|
||||
outf("}\n")
|
||||
outf("// Create markers for unimported std lib packages. Only for use by this test.\n")
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
package unimported
|
||||
|
||||
func _() {
|
||||
//@complete("", archiveslashtar, archiveslashzip, bufio, bytes, compressslashbzip2, compressslashflate, compressslashgzip, compressslashlzw, compressslashzlib, containerslashheap, containerslashlist, containerslashring, context, crypto, cryptoslashaes, cryptoslashcipher, cryptoslashdes, cryptoslashdsa, cryptoslashecdsa, cryptoslashed25519, cryptoslashelliptic, cryptoslashhmac, cryptoslashmd5, cryptoslashrand, cryptoslashrc4, cryptoslashrsa, cryptoslashsha1, cryptoslashsha256, cryptoslashsha512, cryptoslashsubtle, cryptoslashtls, cryptoslashx509, cryptoslashx509slashpkix, databaseslashsql, databaseslashsqlslashdriver, debugslashdwarf, debugslashelf, debugslashgosym, debugslashmacho, debugslashpe, debugslashplan9obj, encoding, encodingslashascii85, encodingslashasn1, encodingslashbase32, encodingslashbase64, encodingslashbinary, encodingslashcsv, encodingslashgob, encodingslashhex, encodingslashjson, encodingslashpem, encodingslashxml, errors, expvar, flag, fmt, goslashast, goslashbuild, goslashconstant, goslashdoc, goslashformat, goslashimporter, goslashparser, goslashprinter, goslashscanner, goslashtoken, goslashtypes, hash, hashslashadler32, hashslashcrc32, hashslashcrc64, hashslashfnv, html, htmlslashtemplate, image, imageslashcolor, imageslashcolorslashpalette, imageslashdraw, imageslashgif, imageslashjpeg, imageslashpng, indexslashsuffixarray, io, ioslashioutil, log, logslashsyslog, math, mathslashbig, mathslashbits, mathslashcmplx, mathslashrand, mime, mimeslashmultipart, mimeslashquotedprintable, net, netslashhttp, netslashhttpslashcgi, netslashhttpslashcookiejar, netslashhttpslashfcgi, netslashhttpslashhttptest, netslashhttpslashhttptrace, netslashhttpslashhttputil, netslashhttpslashpprof, netslashmail, netslashrpc, netslashrpcslashjsonrpc, netslashsmtp, netslashtextproto, netslashurl, os, osslashexec, osslashsignal, osslashuser, path, pathslashfilepath, plugin, reflect, regexp, regexpslashsyntax, runtime, runtimeslashdebug, runtimeslashpprof, runtimeslashtrace, sort, strconv, strings, sync, syncslashatomic, syscall, syscallslashjs, testing, testingslashiotest, testingslashquick, textslashscanner, textslashtabwriter, textslashtemplate, textslashtemplateslashparse, time, unicode, unicodeslashutf16, unicodeslashutf8, unsafe)
|
||||
//@unimported("", archiveslashtar, archiveslashzip, bufio, bytes, compressslashbzip2, compressslashflate, compressslashgzip, compressslashlzw, compressslashzlib, containerslashheap, containerslashlist, containerslashring, context, crypto, cryptoslashaes, cryptoslashcipher, cryptoslashdes, cryptoslashdsa, cryptoslashecdsa, cryptoslashed25519, cryptoslashelliptic, cryptoslashhmac, cryptoslashmd5, cryptoslashrand, cryptoslashrc4, cryptoslashrsa, cryptoslashsha1, cryptoslashsha256, cryptoslashsha512, cryptoslashsubtle, cryptoslashtls, cryptoslashx509, cryptoslashx509slashpkix, databaseslashsql, databaseslashsqlslashdriver, debugslashdwarf, debugslashelf, debugslashgosym, debugslashmacho, debugslashpe, debugslashplan9obj, encoding, encodingslashascii85, encodingslashasn1, encodingslashbase32, encodingslashbase64, encodingslashbinary, encodingslashcsv, encodingslashgob, encodingslashhex, encodingslashjson, encodingslashpem, encodingslashxml, errors, expvar, flag, fmt, goslashast, goslashbuild, goslashconstant, goslashdoc, goslashformat, goslashimporter, goslashparser, goslashprinter, goslashscanner, goslashtoken, goslashtypes, hash, hashslashadler32, hashslashcrc32, hashslashcrc64, hashslashfnv, html, htmlslashtemplate, image, imageslashcolor, imageslashcolorslashpalette, imageslashdraw, imageslashgif, imageslashjpeg, imageslashpng, indexslashsuffixarray, io, ioslashioutil, log, logslashsyslog, math, mathslashbig, mathslashbits, mathslashcmplx, mathslashrand, mime, mimeslashmultipart, mimeslashquotedprintable, net, netslashhttp, netslashhttpslashcgi, netslashhttpslashcookiejar, netslashhttpslashfcgi, netslashhttpslashhttptest, netslashhttpslashhttptrace, netslashhttpslashhttputil, netslashhttpslashpprof, netslashmail, netslashrpc, netslashrpcslashjsonrpc, netslashsmtp, netslashtextproto, netslashurl, os, osslashexec, osslashsignal, osslashuser, path, pathslashfilepath, plugin, reflect, regexp, regexpslashsyntax, runtime, runtimeslashdebug, runtimeslashpprof, runtimeslashtrace, sort, strconv, strings, sync, syncslashatomic, syscall, syscallslashjs, testing, testingslashiotest, testingslashquick, textslashscanner, textslashtabwriter, textslashtemplate, textslashtemplateslashparse, time, unicode, unicodeslashutf16, unicodeslashutf8, unsafe)
|
||||
}
|
||||
|
||||
// Create markers for unimported std lib packages. Only for use by this test.
|
||||
|
193
internal/lsp/tests/completion.go
Normal file
193
internal/lsp/tests/completion.go
Normal file
@ -0,0 +1,193 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
)
|
||||
|
||||
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 {
|
||||
return protocol.CompletionItem{
|
||||
Label: item.Label,
|
||||
Kind: toProtocolCompletionItemKind(item.Kind),
|
||||
Detail: item.Detail,
|
||||
Documentation: item.Documentation,
|
||||
InsertText: item.InsertText,
|
||||
TextEdit: &protocol.TextEdit{
|
||||
NewText: item.Snippet(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func toProtocolCompletionItemKind(kind source.CompletionItemKind) protocol.CompletionItemKind {
|
||||
switch kind {
|
||||
case source.InterfaceCompletionItem:
|
||||
return protocol.InterfaceCompletion
|
||||
case source.StructCompletionItem:
|
||||
return protocol.StructCompletion
|
||||
case source.TypeCompletionItem:
|
||||
return protocol.TypeParameterCompletion // ??
|
||||
case source.ConstantCompletionItem:
|
||||
return protocol.ConstantCompletion
|
||||
case source.FieldCompletionItem:
|
||||
return protocol.FieldCompletion
|
||||
case source.ParameterCompletionItem, source.VariableCompletionItem:
|
||||
return protocol.VariableCompletion
|
||||
case source.FunctionCompletionItem:
|
||||
return protocol.FunctionCompletion
|
||||
case source.MethodCompletionItem:
|
||||
return protocol.MethodCompletion
|
||||
case source.PackageCompletionItem:
|
||||
return protocol.ModuleCompletion // ??
|
||||
default:
|
||||
return protocol.TextCompletion
|
||||
}
|
||||
}
|
||||
|
||||
func FilterBuiltins(items []protocol.CompletionItem) []protocol.CompletionItem {
|
||||
var got []protocol.CompletionItem
|
||||
for _, item := range items {
|
||||
if isBuiltin(item.Label, item.Detail, item.Kind) {
|
||||
continue
|
||||
}
|
||||
got = append(got, item)
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool {
|
||||
if detail == "" && kind == protocol.TypeParameterCompletion {
|
||||
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) string {
|
||||
var (
|
||||
matchedIdxs []int
|
||||
lastGotIdx int
|
||||
inOrder = true
|
||||
)
|
||||
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
|
||||
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, "completions out of order")
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
91
internal/lsp/tests/diagnostics.go
Normal file
91
internal/lsp/tests/diagnostics.go
Normal file
@ -0,0 +1,91 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"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 {
|
||||
sortDiagnostics(want)
|
||||
sortDiagnostics(got)
|
||||
|
||||
if len(got) != len(want) {
|
||||
return summarizeDiagnostics(-1, 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, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
|
||||
}
|
||||
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 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)
|
||||
}
|
||||
} else 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, 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 ""
|
||||
}
|
||||
|
||||
func sortDiagnostics(d []source.Diagnostic) {
|
||||
sort.Slice(d, func(i int, j int) bool {
|
||||
return compareDiagnostic(d[i], d[j]) < 0
|
||||
})
|
||||
}
|
||||
|
||||
func compareDiagnostic(a, b source.Diagnostic) int {
|
||||
if r := span.CompareURI(a.URI, b.URI); r != 0 {
|
||||
return r
|
||||
}
|
||||
if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
|
||||
return r
|
||||
}
|
||||
if a.Message < b.Message {
|
||||
return -1
|
||||
}
|
||||
if a.Message == b.Message {
|
||||
return 0
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func summarizeDiagnostics(i int, 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", d.URI, d.Range, d.Message)
|
||||
}
|
||||
fmt.Fprintf(msg, "got:\n")
|
||||
for _, d := range got {
|
||||
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
||||
}
|
||||
return msg.String()
|
||||
}
|
@ -2,13 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tests exports functionality to be used across a variety of gopls tests.
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
@ -30,22 +29,26 @@ import (
|
||||
// We hardcode the expected number of test cases to ensure that all tests
|
||||
// are being executed. If a test is added, this number must be changed.
|
||||
const (
|
||||
ExpectedCompletionsCount = 165
|
||||
ExpectedCompletionSnippetCount = 35
|
||||
ExpectedDiagnosticsCount = 21
|
||||
ExpectedFormatCount = 6
|
||||
ExpectedImportCount = 2
|
||||
ExpectedSuggestedFixCount = 1
|
||||
ExpectedDefinitionsCount = 39
|
||||
ExpectedTypeDefinitionsCount = 2
|
||||
ExpectedFoldingRangesCount = 2
|
||||
ExpectedHighlightsCount = 2
|
||||
ExpectedReferencesCount = 6
|
||||
ExpectedRenamesCount = 20
|
||||
ExpectedPrepareRenamesCount = 8
|
||||
ExpectedSymbolsCount = 1
|
||||
ExpectedSignaturesCount = 21
|
||||
ExpectedLinksCount = 4
|
||||
ExpectedCompletionsCount = 152
|
||||
ExpectedCompletionSnippetCount = 35
|
||||
ExpectedUnimportedCompletionsCount = 1
|
||||
ExpectedDeepCompletionsCount = 5
|
||||
ExpectedFuzzyCompletionsCount = 6
|
||||
ExpectedRankedCompletionsCount = 1
|
||||
ExpectedDiagnosticsCount = 21
|
||||
ExpectedFormatCount = 6
|
||||
ExpectedImportCount = 2
|
||||
ExpectedSuggestedFixCount = 1
|
||||
ExpectedDefinitionsCount = 39
|
||||
ExpectedTypeDefinitionsCount = 2
|
||||
ExpectedFoldingRangesCount = 2
|
||||
ExpectedHighlightsCount = 2
|
||||
ExpectedReferencesCount = 6
|
||||
ExpectedRenamesCount = 20
|
||||
ExpectedPrepareRenamesCount = 8
|
||||
ExpectedSymbolsCount = 1
|
||||
ExpectedSignaturesCount = 21
|
||||
ExpectedLinksCount = 4
|
||||
)
|
||||
|
||||
const (
|
||||
@ -61,6 +64,10 @@ type Diagnostics map[span.URI][]source.Diagnostic
|
||||
type CompletionItems map[token.Pos]*source.CompletionItem
|
||||
type Completions map[span.Span]Completion
|
||||
type CompletionSnippets map[span.Span]CompletionSnippet
|
||||
type UnimportedCompletions map[span.Span]Completion
|
||||
type DeepCompletions map[span.Span]Completion
|
||||
type FuzzyCompletions map[span.Span]Completion
|
||||
type RankCompletions map[span.Span]Completion
|
||||
type FoldingRanges []span.Span
|
||||
type Formats []span.Span
|
||||
type Imports []span.Span
|
||||
@ -76,25 +83,29 @@ type Signatures map[span.Span]*source.SignatureInformation
|
||||
type Links map[span.URI][]Link
|
||||
|
||||
type Data struct {
|
||||
Config packages.Config
|
||||
Exported *packagestest.Exported
|
||||
Diagnostics Diagnostics
|
||||
CompletionItems CompletionItems
|
||||
Completions Completions
|
||||
CompletionSnippets CompletionSnippets
|
||||
FoldingRanges FoldingRanges
|
||||
Formats Formats
|
||||
Imports Imports
|
||||
SuggestedFixes SuggestedFixes
|
||||
Definitions Definitions
|
||||
Highlights Highlights
|
||||
References References
|
||||
Renames Renames
|
||||
PrepareRenames PrepareRenames
|
||||
Symbols Symbols
|
||||
symbolsChildren SymbolsChildren
|
||||
Signatures Signatures
|
||||
Links Links
|
||||
Config packages.Config
|
||||
Exported *packagestest.Exported
|
||||
Diagnostics Diagnostics
|
||||
CompletionItems CompletionItems
|
||||
Completions Completions
|
||||
CompletionSnippets CompletionSnippets
|
||||
UnimportedCompletions UnimportedCompletions
|
||||
DeepCompletions DeepCompletions
|
||||
FuzzyCompletions FuzzyCompletions
|
||||
RankCompletions RankCompletions
|
||||
FoldingRanges FoldingRanges
|
||||
Formats Formats
|
||||
Imports Imports
|
||||
SuggestedFixes SuggestedFixes
|
||||
Definitions Definitions
|
||||
Highlights Highlights
|
||||
References References
|
||||
Renames Renames
|
||||
PrepareRenames PrepareRenames
|
||||
Symbols Symbols
|
||||
symbolsChildren SymbolsChildren
|
||||
Signatures Signatures
|
||||
Links Links
|
||||
|
||||
t testing.TB
|
||||
fragments map[string]string
|
||||
@ -107,7 +118,12 @@ type Data struct {
|
||||
|
||||
type Tests interface {
|
||||
Diagnostics(*testing.T, Diagnostics)
|
||||
Completion(*testing.T, Completions, CompletionSnippets, CompletionItems)
|
||||
Completion(*testing.T, Completions, CompletionItems)
|
||||
CompletionSnippets(*testing.T, CompletionSnippets, CompletionItems)
|
||||
UnimportedCompletions(*testing.T, UnimportedCompletions, CompletionItems)
|
||||
DeepCompletions(*testing.T, DeepCompletions, CompletionItems)
|
||||
FuzzyCompletions(*testing.T, FuzzyCompletions, CompletionItems)
|
||||
RankCompletions(*testing.T, RankCompletions, CompletionItems)
|
||||
FoldingRange(*testing.T, FoldingRanges)
|
||||
Format(*testing.T, Formats)
|
||||
Import(*testing.T, Imports)
|
||||
@ -132,16 +148,24 @@ type Definition struct {
|
||||
type CompletionTestType int
|
||||
|
||||
const (
|
||||
// Full means candidates in test must match full list of candidates.
|
||||
CompletionFull CompletionTestType = iota
|
||||
// Default runs the standard completion tests.
|
||||
CompletionDefault = CompletionTestType(iota)
|
||||
|
||||
// Partial means candidates in test must be valid and in the right relative order.
|
||||
CompletionPartial
|
||||
// Unimported tests the autocompletion of unimported packages.
|
||||
CompletionUnimported
|
||||
|
||||
// Deep tests deep completion.
|
||||
CompletionDeep
|
||||
|
||||
// Fuzzy tests deep completion and fuzzy matching.
|
||||
CompletionFuzzy
|
||||
|
||||
// CompletionRank candidates in test must be valid and in the right relative order.
|
||||
CompletionRank
|
||||
)
|
||||
|
||||
type Completion struct {
|
||||
CompletionItems []token.Pos
|
||||
Type CompletionTestType
|
||||
}
|
||||
|
||||
type CompletionSnippet struct {
|
||||
@ -166,23 +190,42 @@ func Context(t testing.TB) context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func DefaultOptions() source.Options {
|
||||
o := source.DefaultOptions
|
||||
o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
|
||||
source.Go: {
|
||||
protocol.SourceOrganizeImports: true,
|
||||
protocol.QuickFix: true,
|
||||
},
|
||||
source.Mod: {},
|
||||
source.Sum: {},
|
||||
}
|
||||
o.HoverKind = source.SynopsisDocumentation
|
||||
o.InsertTextFormat = protocol.SnippetTextFormat
|
||||
return o
|
||||
}
|
||||
|
||||
func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||
t.Helper()
|
||||
|
||||
data := &Data{
|
||||
Diagnostics: make(Diagnostics),
|
||||
CompletionItems: make(CompletionItems),
|
||||
Completions: make(Completions),
|
||||
CompletionSnippets: make(CompletionSnippets),
|
||||
Definitions: make(Definitions),
|
||||
Highlights: make(Highlights),
|
||||
References: make(References),
|
||||
Renames: make(Renames),
|
||||
PrepareRenames: make(PrepareRenames),
|
||||
Symbols: make(Symbols),
|
||||
symbolsChildren: make(SymbolsChildren),
|
||||
Signatures: make(Signatures),
|
||||
Links: make(Links),
|
||||
Diagnostics: make(Diagnostics),
|
||||
CompletionItems: make(CompletionItems),
|
||||
Completions: make(Completions),
|
||||
CompletionSnippets: make(CompletionSnippets),
|
||||
UnimportedCompletions: make(UnimportedCompletions),
|
||||
DeepCompletions: make(DeepCompletions),
|
||||
FuzzyCompletions: make(FuzzyCompletions),
|
||||
RankCompletions: make(RankCompletions),
|
||||
Definitions: make(Definitions),
|
||||
Highlights: make(Highlights),
|
||||
References: make(References),
|
||||
Renames: make(Renames),
|
||||
PrepareRenames: make(PrepareRenames),
|
||||
Symbols: make(Symbols),
|
||||
symbolsChildren: make(SymbolsChildren),
|
||||
Signatures: make(Signatures),
|
||||
Links: make(Links),
|
||||
|
||||
t: t,
|
||||
dir: dir,
|
||||
@ -226,7 +269,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||
},
|
||||
}
|
||||
data.Exported = packagestest.Export(t, exporter, modules)
|
||||
for fragment, _ := range files {
|
||||
for fragment := range files {
|
||||
filename := data.Exported.File(testModule, fragment)
|
||||
data.fragments[filename] = fragment
|
||||
}
|
||||
@ -252,25 +295,30 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||
|
||||
// Collect any data that needs to be used by subsequent tests.
|
||||
if err := data.Exported.Expect(map[string]interface{}{
|
||||
"diag": data.collectDiagnostics,
|
||||
"item": data.collectCompletionItems,
|
||||
"complete": data.collectCompletions(CompletionFull),
|
||||
"completePartial": data.collectCompletions(CompletionPartial),
|
||||
"fold": data.collectFoldingRanges,
|
||||
"format": data.collectFormats,
|
||||
"import": data.collectImports,
|
||||
"godef": data.collectDefinitions,
|
||||
"typdef": data.collectTypeDefinitions,
|
||||
"hover": data.collectHoverDefinitions,
|
||||
"highlight": data.collectHighlights,
|
||||
"refs": data.collectReferences,
|
||||
"rename": data.collectRenames,
|
||||
"prepare": data.collectPrepareRenames,
|
||||
"symbol": data.collectSymbols,
|
||||
"signature": data.collectSignatures,
|
||||
"snippet": data.collectCompletionSnippets,
|
||||
"link": data.collectLinks,
|
||||
"suggestedfix": data.collectSuggestedFixes,
|
||||
"diag": data.collectDiagnostics,
|
||||
"item": data.collectCompletionItems,
|
||||
"complete": data.collectCompletions(CompletionDefault),
|
||||
"unimported": data.collectCompletions(CompletionUnimported),
|
||||
"deep": data.collectCompletions(CompletionDeep),
|
||||
"fuzzy": data.collectCompletions(CompletionFuzzy),
|
||||
"rank": data.collectCompletions(CompletionRank),
|
||||
"snippet": data.collectCompletionSnippets,
|
||||
"fold": data.collectFoldingRanges,
|
||||
"format": data.collectFormats,
|
||||
"import": data.collectImports,
|
||||
"godef": data.collectDefinitions,
|
||||
"typdef": data.collectTypeDefinitions,
|
||||
"hover": data.collectHoverDefinitions,
|
||||
"highlight": data.collectHighlights,
|
||||
"refs": data.collectReferences,
|
||||
"rename": data.collectRenames,
|
||||
"prepare": data.collectPrepareRenames,
|
||||
"symbol": data.collectSymbols,
|
||||
"signature": data.collectSignatures,
|
||||
|
||||
// LSP-only features.
|
||||
"link": data.collectLinks,
|
||||
"suggestedfix": data.collectSuggestedFixes,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -292,15 +340,56 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||
|
||||
func Run(t *testing.T, tests Tests, data *Data) {
|
||||
t.Helper()
|
||||
|
||||
t.Run("Completion", func(t *testing.T) {
|
||||
t.Helper()
|
||||
if len(data.Completions) != ExpectedCompletionsCount {
|
||||
t.Errorf("got %v completions expected %v", len(data.Completions), ExpectedCompletionsCount)
|
||||
}
|
||||
tests.Completion(t, data.Completions, data.CompletionItems)
|
||||
})
|
||||
|
||||
t.Run("CompletionSnippets", func(t *testing.T) {
|
||||
t.Helper()
|
||||
if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
|
||||
t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
|
||||
}
|
||||
tests.Completion(t, data.Completions, data.CompletionSnippets, data.CompletionItems)
|
||||
if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
|
||||
t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
|
||||
}
|
||||
tests.CompletionSnippets(t, data.CompletionSnippets, data.CompletionItems)
|
||||
})
|
||||
|
||||
t.Run("UnimportedCompletion", func(t *testing.T) {
|
||||
t.Helper()
|
||||
if len(data.UnimportedCompletions) != ExpectedUnimportedCompletionsCount {
|
||||
t.Errorf("got %v unimported completions expected %v", len(data.UnimportedCompletions), ExpectedUnimportedCompletionsCount)
|
||||
}
|
||||
tests.UnimportedCompletions(t, data.UnimportedCompletions, data.CompletionItems)
|
||||
})
|
||||
|
||||
t.Run("DeepCompletion", func(t *testing.T) {
|
||||
t.Helper()
|
||||
if len(data.DeepCompletions) != ExpectedDeepCompletionsCount {
|
||||
t.Errorf("got %v deep completions expected %v", len(data.DeepCompletions), ExpectedDeepCompletionsCount)
|
||||
}
|
||||
tests.DeepCompletions(t, data.DeepCompletions, data.CompletionItems)
|
||||
})
|
||||
|
||||
t.Run("FuzzyCompletion", func(t *testing.T) {
|
||||
t.Helper()
|
||||
if len(data.FuzzyCompletions) != ExpectedFuzzyCompletionsCount {
|
||||
t.Errorf("got %v fuzzy completions expected %v", len(data.FuzzyCompletions), ExpectedFuzzyCompletionsCount)
|
||||
}
|
||||
tests.FuzzyCompletions(t, data.FuzzyCompletions, data.CompletionItems)
|
||||
})
|
||||
|
||||
t.Run("RankCompletions", func(t *testing.T) {
|
||||
t.Helper()
|
||||
if len(data.RankCompletions) != ExpectedRankedCompletionsCount {
|
||||
t.Errorf("got %v fuzzy completions expected %v", len(data.RankCompletions), ExpectedRankedCompletionsCount)
|
||||
}
|
||||
tests.RankCompletions(t, data.RankCompletions, data.CompletionItems)
|
||||
})
|
||||
|
||||
t.Run("Diagnostics", func(t *testing.T) {
|
||||
@ -528,90 +617,32 @@ func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
|
||||
data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
|
||||
}
|
||||
|
||||
// diffDiagnostics prints the diff between expected and actual diagnostics test
|
||||
// results.
|
||||
func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
|
||||
sortDiagnostics(want)
|
||||
sortDiagnostics(got)
|
||||
|
||||
if len(got) != len(want) {
|
||||
return summarizeDiagnostics(-1, 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, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
|
||||
}
|
||||
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 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)
|
||||
}
|
||||
} else 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, 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 ""
|
||||
}
|
||||
|
||||
func sortDiagnostics(d []source.Diagnostic) {
|
||||
sort.Slice(d, func(i int, j int) bool {
|
||||
return compareDiagnostic(d[i], d[j]) < 0
|
||||
})
|
||||
}
|
||||
|
||||
func compareDiagnostic(a, b source.Diagnostic) int {
|
||||
if r := span.CompareURI(a.URI, b.URI); r != 0 {
|
||||
return r
|
||||
}
|
||||
if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
|
||||
return r
|
||||
}
|
||||
if a.Message < b.Message {
|
||||
return -1
|
||||
}
|
||||
if a.Message == b.Message {
|
||||
return 0
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func summarizeDiagnostics(i int, 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", d.URI, d.Range, d.Message)
|
||||
}
|
||||
fmt.Fprintf(msg, "got:\n")
|
||||
for _, d := range got {
|
||||
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
||||
}
|
||||
return msg.String()
|
||||
}
|
||||
|
||||
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
|
||||
return func(src span.Span, expected []token.Pos) {
|
||||
data.Completions[src] = Completion{
|
||||
result := func(m map[span.Span]Completion, src span.Span, expected []token.Pos) {
|
||||
m[src] = Completion{
|
||||
CompletionItems: expected,
|
||||
Type: typ,
|
||||
}
|
||||
}
|
||||
switch typ {
|
||||
case CompletionDeep:
|
||||
return func(src span.Span, expected []token.Pos) {
|
||||
result(data.DeepCompletions, src, expected)
|
||||
}
|
||||
case CompletionUnimported:
|
||||
return func(src span.Span, expected []token.Pos) {
|
||||
result(data.UnimportedCompletions, src, expected)
|
||||
}
|
||||
case CompletionFuzzy:
|
||||
return func(src span.Span, expected []token.Pos) {
|
||||
result(data.FuzzyCompletions, src, expected)
|
||||
}
|
||||
case CompletionRank:
|
||||
return func(src span.Span, expected []token.Pos) {
|
||||
result(data.RankCompletions, src, expected)
|
||||
}
|
||||
default:
|
||||
return func(src span.Span, expected []token.Pos) {
|
||||
result(data.Completions, src, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user