From c7cf430b80474d52799a3a45d3f6665a424cc476 Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Thu, 26 Sep 2019 13:56:23 -0400 Subject: [PATCH] internal/lsp: lift the test loops out into the testing framework The loops are common to all the testing layers, so lift them. This prepares for more test improvements, without any funcitonal changes. Change-Id: Ib750c8a7bb4c424a185cb0bd841674a69db1385b Reviewed-on: https://go-review.googlesource.com/c/tools/+/197717 Run-TryBot: Ian Cottrell TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/cmd/test/check.go | 86 ++- internal/lsp/cmd/test/cmdtest.go | 35 +- internal/lsp/cmd/test/definition.go | 58 +- internal/lsp/cmd/test/format.go | 52 +- internal/lsp/cmd/test/rename.go | 65 +- internal/lsp/completion_test.go | 174 +++-- internal/lsp/lsp_test.go | 928 ++++++++++++------------ internal/lsp/source/source_test.go | 1015 +++++++++++++-------------- internal/lsp/tests/tests.go | 122 ++-- 9 files changed, 1238 insertions(+), 1297 deletions(-) diff --git a/internal/lsp/cmd/test/check.go b/internal/lsp/cmd/test/check.go index 78ea9d508a..12a5658682 100644 --- a/internal/lsp/cmd/test/check.go +++ b/internal/lsp/cmd/test/check.go @@ -11,60 +11,58 @@ import ( "testing" "golang.org/x/tools/internal/lsp/cmd" - "golang.org/x/tools/internal/lsp/tests" + "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/tool" ) -func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) { - for uri, want := range data { - if len(want) == 1 && want[0].Message == "" { +func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnostic) { + if len(want) == 1 && want[0].Message == "" { + return + } + fname := uri.Filename() + args := []string{"-remote=internal", "check", fname} + app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env) + out := CaptureStdOut(t, func() { + _ = tool.Run(r.ctx, app, args) + }) + // parse got into a collection of reports + got := map[string]struct{}{} + for _, l := range strings.Split(out, "\n") { + if len(l) == 0 { continue } - fname := uri.Filename() - args := []string{"-remote=internal", "check", fname} - app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env) - out := CaptureStdOut(t, func() { - _ = tool.Run(r.ctx, app, args) - }) - // parse got into a collection of reports - got := map[string]struct{}{} - for _, l := range strings.Split(out, "\n") { - if len(l) == 0 { - continue + // parse and reprint to normalize the span + bits := strings.SplitN(l, ": ", 2) + if len(bits) == 2 { + spn := span.Parse(strings.TrimSpace(bits[0])) + spn = span.New(spn.URI(), spn.Start(), span.Point{}) + data, err := ioutil.ReadFile(fname) + if err != nil { + t.Fatal(err) } - // parse and reprint to normalize the span - bits := strings.SplitN(l, ": ", 2) - if len(bits) == 2 { - spn := span.Parse(strings.TrimSpace(bits[0])) - spn = span.New(spn.URI(), spn.Start(), span.Point{}) - data, err := ioutil.ReadFile(fname) - if err != nil { - t.Fatal(err) - } - converter := span.NewContentConverter(fname, data) - s, err := spn.WithPosition(converter) - if err != nil { - t.Fatal(err) - } - l = fmt.Sprintf("%s: %s", s, strings.TrimSpace(bits[1])) + converter := span.NewContentConverter(fname, data) + s, err := spn.WithPosition(converter) + if err != nil { + t.Fatal(err) } - got[l] = struct{}{} + l = fmt.Sprintf("%s: %s", s, strings.TrimSpace(bits[1])) } - for _, diag := range want { - expect := fmt.Sprintf("%v:%v:%v: %v", diag.URI.Filename(), diag.Range.Start.Line+1, diag.Range.Start.Character+1, diag.Message) - if diag.Range.Start.Character == 0 { - expect = fmt.Sprintf("%v:%v: %v", diag.URI.Filename(), diag.Range.Start.Line+1, diag.Message) - } - _, found := got[expect] - if !found { - t.Errorf("missing diagnostic %q", expect) - } else { - delete(got, expect) - } + got[l] = struct{}{} + } + for _, diag := range want { + expect := fmt.Sprintf("%v:%v:%v: %v", diag.URI.Filename(), diag.Range.Start.Line+1, diag.Range.Start.Character+1, diag.Message) + if diag.Range.Start.Character == 0 { + expect = fmt.Sprintf("%v:%v: %v", diag.URI.Filename(), diag.Range.Start.Line+1, diag.Message) } - for extra, _ := range got { - t.Errorf("extra diagnostic %q", extra) + _, found := got[expect] + if !found { + t.Errorf("missing diagnostic %q", expect) + } else { + delete(got, expect) } } + for extra, _ := range got { + t.Errorf("extra diagnostic %q", extra) + } } diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go index c04bb9138c..13607f2a67 100644 --- a/internal/lsp/cmd/test/cmdtest.go +++ b/internal/lsp/cmd/test/cmdtest.go @@ -16,7 +16,10 @@ import ( "testing" "golang.org/x/tools/go/packages/packagestest" + "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" ) type runner struct { @@ -33,67 +36,67 @@ func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Con } } -func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) { +func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, 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) { +func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, 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) { +func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, 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) { +func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, 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) { +func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { //TODO: add command line completions tests when it works } -func (r *runner) CaseSensitiveCompletions(t *testing.T, data tests.CaseSensitiveCompletions, items tests.CompletionItems) { +func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, 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) { +func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { //TODO: add command line completions tests when it works } -func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) { +func (r *runner) FoldingRange(t *testing.T, spn span.Span) { //TODO: add command line folding range tests when it works } -func (r *runner) Highlight(t *testing.T, data tests.Highlights) { +func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) { //TODO: add command line highlight tests when it works } -func (r *runner) Reference(t *testing.T, data tests.References) { +func (r *runner) Reference(t *testing.T, src span.Span, itemList []span.Span) { //TODO: add command line references tests when it works } -func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) { +func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) { //TODO: add command line prepare rename tests when it works } -func (r *runner) Symbol(t *testing.T, data tests.Symbols) { +func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { //TODO: add command line symbol tests when it works } -func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { +func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *source.SignatureInformation) { //TODO: add command line signature tests when it works } -func (r *runner) Link(t *testing.T, data tests.Links) { +func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) { //TODO: add command line link tests when it works } -func (r *runner) Import(t *testing.T, data tests.Imports) { +func (r *runner) Import(t *testing.T, spn span.Span) { //TODO: add command line imports tests when it works } -func (r *runner) SuggestedFix(t *testing.T, data tests.SuggestedFixes) { +func (r *runner) SuggestedFix(t *testing.T, spn span.Span) { //TODO: add suggested fix tests when it works } diff --git a/internal/lsp/cmd/test/definition.go b/internal/lsp/cmd/test/definition.go index 2857dda926..6bbcdd15be 100644 --- a/internal/lsp/cmd/test/definition.go +++ b/internal/lsp/cmd/test/definition.go @@ -33,41 +33,39 @@ var godefModes = []godefMode{ jsonGoDef, } -func (r *runner) Definition(t *testing.T, data tests.Definitions) { +func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) { // TODO: https://golang.org/issue/32794. if !*tests.UpdateGolden { t.Skip() } - for _, d := range data { - if d.IsType || d.OnlyHover { - // TODO: support type definition, hover queries - continue + if d.IsType || d.OnlyHover { + // TODO: support type definition, hover queries + return + } + d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{}) + for _, mode := range godefModes { + args := []string{"-remote=internal", "query"} + tag := d.Name + "-definition" + if mode&jsonGoDef != 0 { + tag += "-json" + args = append(args, "-json") } - d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{}) - for _, mode := range godefModes { - args := []string{"-remote=internal", "query"} - tag := d.Name + "-definition" - if mode&jsonGoDef != 0 { - tag += "-json" - args = append(args, "-json") - } - args = append(args, "definition") - uri := d.Src.URI() - args = append(args, fmt.Sprint(d.Src)) - got := CaptureStdOut(t, func() { - app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env) - _ = tool.Run(r.ctx, app, args) - }) - got = normalizePaths(r.data, got) - if mode&jsonGoDef != 0 && runtime.GOOS == "windows" { - got = strings.Replace(got, "file:///", "file://", -1) - } - expect := strings.TrimSpace(string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) { - return []byte(got), nil - }))) - if expect != "" && !strings.HasPrefix(got, expect) { - t.Errorf("definition %v failed with %#v expected:\n%q\ngot:\n%q", tag, args, expect, got) - } + args = append(args, "definition") + uri := d.Src.URI() + args = append(args, fmt.Sprint(d.Src)) + got := CaptureStdOut(t, func() { + app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env) + _ = tool.Run(r.ctx, app, args) + }) + got = normalizePaths(r.data, got) + if mode&jsonGoDef != 0 && runtime.GOOS == "windows" { + got = strings.Replace(got, "file:///", "file://", -1) + } + expect := strings.TrimSpace(string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) { + return []byte(got), nil + }))) + if expect != "" && !strings.HasPrefix(got, expect) { + t.Errorf("definition %v failed with %#v expected:\n%q\ngot:\n%q", tag, args, expect, got) } } } diff --git a/internal/lsp/cmd/test/format.go b/internal/lsp/cmd/test/format.go index 11e34198c3..9442727a84 100644 --- a/internal/lsp/cmd/test/format.go +++ b/internal/lsp/cmd/test/format.go @@ -11,7 +11,7 @@ import ( "testing" "golang.org/x/tools/internal/lsp/cmd" - "golang.org/x/tools/internal/lsp/tests" + "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/tool" ) @@ -20,32 +20,30 @@ var formatModes = [][]string{ []string{"-d"}, } -func (r *runner) Format(t *testing.T, data tests.Formats) { - for _, spn := range data { - for _, mode := range formatModes { - tag := "gofmt" + strings.Join(mode, "") - uri := spn.URI() - filename := uri.Filename() - args := append(mode, filename) - expect := string(r.data.Golden(tag, filename, func() ([]byte, error) { - cmd := exec.Command("gofmt", args...) - contents, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files - contents = []byte(normalizePaths(r.data, fixFileHeader(string(contents)))) - return contents, nil - })) - if expect == "" { - //TODO: our error handling differs, for now just skip unformattable files - continue - } - app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env) - got := CaptureStdOut(t, func() { - _ = tool.Run(r.ctx, app, append([]string{"-remote=internal", "format"}, args...)) - }) - got = normalizePaths(r.data, got) - // check the first two lines are the expected file header - if expect != got { - t.Errorf("format failed with %#v expected:\n%s\ngot:\n%s", args, expect, got) - } +func (r *runner) Format(t *testing.T, spn span.Span) { + for _, mode := range formatModes { + tag := "gofmt" + strings.Join(mode, "") + uri := spn.URI() + filename := uri.Filename() + args := append(mode, filename) + expect := string(r.data.Golden(tag, filename, func() ([]byte, error) { + cmd := exec.Command("gofmt", args...) + contents, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files + contents = []byte(normalizePaths(r.data, fixFileHeader(string(contents)))) + return contents, nil + })) + if expect == "" { + //TODO: our error handling differs, for now just skip unformattable files + continue + } + app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env) + got := CaptureStdOut(t, func() { + _ = tool.Run(r.ctx, app, append([]string{"-remote=internal", "format"}, args...)) + }) + got = normalizePaths(r.data, got) + // check the first two lines are the expected file header + if expect != got { + t.Errorf("format failed with %#v expected:\n%s\ngot:\n%s", args, expect, got) } } } diff --git a/internal/lsp/cmd/test/rename.go b/internal/lsp/cmd/test/rename.go index 16fcb086d6..f36ac8fd78 100644 --- a/internal/lsp/cmd/test/rename.go +++ b/internal/lsp/cmd/test/rename.go @@ -6,12 +6,10 @@ package cmdtest import ( "fmt" - "sort" "strings" "testing" "golang.org/x/tools/internal/lsp/cmd" - "golang.org/x/tools/internal/lsp/tests" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/tool" ) @@ -21,45 +19,30 @@ var renameModes = [][]string{ []string{"-d"}, } -func (r *runner) Rename(t *testing.T, data tests.Renames) { - sortedSpans := sortSpans(data) // run the tests in a repeatable order - for _, spn := range sortedSpans { - tag := data[spn] - filename := spn.URI().Filename() - for _, mode := range renameModes { - goldenTag := data[spn] + strings.Join(mode, "") + "-rename" - app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env) - loc := fmt.Sprintf("%v", spn) - args := []string{"-remote=internal", "rename"} - if strings.Join(mode, "") != "" { - args = append(args, strings.Join(mode, "")) - } - args = append(args, loc, tag) - var err error - got := CaptureStdOut(t, func() { - err = tool.Run(r.ctx, app, args) - }) - if err != nil { - got = err.Error() - } - got = normalizePaths(r.data, got) - expect := string(r.data.Golden(goldenTag, filename, func() ([]byte, error) { - return []byte(got), nil - })) - if expect != got { - t.Errorf("rename failed with %#v expected:\n%s\ngot:\n%s", args, expect, got) - } +func (r *runner) Rename(t *testing.T, spn span.Span, newText string) { + filename := spn.URI().Filename() + for _, mode := range renameModes { + goldenTag := newText + strings.Join(mode, "") + "-rename" + app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env) + loc := fmt.Sprintf("%v", spn) + args := []string{"-remote=internal", "rename"} + if strings.Join(mode, "") != "" { + args = append(args, strings.Join(mode, "")) + } + args = append(args, loc, newText) + var err error + got := CaptureStdOut(t, func() { + err = tool.Run(r.ctx, app, args) + }) + if err != nil { + got = err.Error() + } + got = normalizePaths(r.data, got) + expect := string(r.data.Golden(goldenTag, filename, func() ([]byte, error) { + return []byte(got), nil + })) + if expect != got { + t.Errorf("rename failed with %#v expected:\n%s\ngot:\n%s", args, expect, got) } } } - -func sortSpans(data map[span.Span]string) []span.Span { - spans := make([]span.Span, 0, len(data)) - for spn, _ := range data { - spans = append(spans, spn) - } - sort.Slice(spans, func(i, j int) bool { - return span.Compare(spans[i], spans[j]) < 0 - }) - return spans -} diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go index d7862184ed..7dff111c7c 100644 --- a/internal/lsp/completion_test.go +++ b/internal/lsp/completion_test.go @@ -11,119 +11,103 @@ import ( "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) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) { + 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) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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) CaseSensitiveCompletions(t *testing.T, data tests.CaseSensitiveCompletions, items tests.CompletionItems) { - for src, test := range data { - got := r.callCompletion(t, src, source.CompletionOptions{ - CaseSensitive: 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) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + got := r.callCompletion(t, src, source.CompletionOptions{ + CaseSensitive: 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) 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 (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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) } } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 2b78bedda5..691621d71f 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -70,71 +70,67 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) { } // TODO: Actually test the LSP diagnostics function in this test. -func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) { +func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnostic) { v := r.server.session.View(viewName) - for uri, want := range data { - f, err := v.GetFile(r.ctx, uri) - if err != nil { - t.Fatalf("no file for %s: %v", f, err) - } - gof, ok := f.(source.GoFile) - if !ok { - t.Fatalf("%s is not a Go file: %v", uri, err) - } - results, _, err := source.Diagnostics(r.ctx, v, gof, nil) - if err != nil { - t.Fatal(err) - } - got := results[uri] - // A special case to test that there are no diagnostics for a file. - if len(want) == 1 && want[0].Source == "no_diagnostics" { - if len(got) != 0 { - t.Errorf("expected no diagnostics for %s, got %v", uri, got) - } - continue - } - if diff := tests.DiffDiagnostics(uri, want, got); diff != "" { - t.Error(diff) + f, err := v.GetFile(r.ctx, uri) + if err != nil { + t.Fatalf("no file for %s: %v", f, err) + } + gof, ok := f.(source.GoFile) + if !ok { + t.Fatalf("%s is not a Go file: %v", uri, err) + } + results, _, err := source.Diagnostics(r.ctx, v, gof, nil) + if err != nil { + t.Fatal(err) + } + got := results[uri] + // A special case to test that there are no diagnostics for a file. + if len(want) == 1 && want[0].Source == "no_diagnostics" { + if len(got) != 0 { + t.Errorf("expected no diagnostics for %s, got %v", uri, got) } + return + } + if diff := tests.DiffDiagnostics(uri, want, got); diff != "" { + t.Error(diff) } } -func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) { - for _, spn := range data { - uri := spn.URI() - view := r.server.session.ViewOf(uri) - original := view.Options() - modified := original +func (r *runner) FoldingRange(t *testing.T, spn span.Span) { + uri := spn.URI() + view := r.server.session.ViewOf(uri) + original := view.Options() + modified := original - // Test all folding ranges. - modified.LineFoldingOnly = false - view.SetOptions(modified) - ranges, err := r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(uri), - }, - }) - if err != nil { - t.Error(err) - continue - } - r.foldingRanges(t, "foldingRange", uri, ranges) - - // Test folding ranges with lineFoldingOnly = true. - modified.LineFoldingOnly = true - view.SetOptions(modified) - ranges, err = r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(uri), - }, - }) - if err != nil { - t.Error(err) - continue - } - r.foldingRanges(t, "foldingRange-lineFolding", uri, ranges) - view.SetOptions(original) + // Test all folding ranges. + modified.LineFoldingOnly = false + view.SetOptions(modified) + ranges, err := r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + }) + if err != nil { + t.Error(err) + return } + r.foldingRanges(t, "foldingRange", uri, ranges) + + // Test folding ranges with lineFoldingOnly = true. + modified.LineFoldingOnly = true + view.SetOptions(modified) + ranges, err = r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + }) + if err != nil { + t.Error(err) + return + } + r.foldingRanges(t, "foldingRange-lineFolding", uri, ranges) + view.SetOptions(original) } func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, ranges []protocol.FoldingRange) { @@ -249,379 +245,363 @@ func foldRanges(m *protocol.ColumnMapper, contents string, ranges []protocol.Fol return res, nil } -func (r *runner) Format(t *testing.T, data tests.Formats) { - for _, spn := range data { - uri := spn.URI() - filename := uri.Filename() - gofmted := string(r.data.Golden("gofmt", filename, func() ([]byte, error) { - cmd := exec.Command("gofmt", filename) - out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files - return out, nil - })) +func (r *runner) Format(t *testing.T, spn span.Span) { + uri := spn.URI() + filename := uri.Filename() + gofmted := string(r.data.Golden("gofmt", filename, func() ([]byte, error) { + cmd := exec.Command("gofmt", filename) + out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files + return out, nil + })) - edits, err := r.server.Formatting(r.ctx, &protocol.DocumentFormattingParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(uri), - }, - }) - if err != nil { - if gofmted != "" { - t.Error(err) - } - continue - } - m, err := r.data.Mapper(uri) - if err != nil { - t.Fatal(err) - } - sedits, err := source.FromProtocolEdits(m, edits) - if err != nil { + edits, err := r.server.Formatting(r.ctx, &protocol.DocumentFormattingParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + }) + if err != nil { + if gofmted != "" { t.Error(err) } - got := diff.ApplyEdits(string(m.Content), sedits) - if gofmted != got { - t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got) - } + return + } + m, err := r.data.Mapper(uri) + if err != nil { + t.Fatal(err) + } + sedits, err := source.FromProtocolEdits(m, edits) + if err != nil { + t.Error(err) + } + got := diff.ApplyEdits(string(m.Content), sedits) + if gofmted != got { + t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got) } } -func (r *runner) Import(t *testing.T, data tests.Imports) { - for _, spn := range data { - uri := spn.URI() - filename := uri.Filename() - goimported := string(r.data.Golden("goimports", filename, func() ([]byte, error) { - cmd := exec.Command("goimports", filename) - out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files - return out, nil - })) +func (r *runner) Import(t *testing.T, spn span.Span) { + uri := spn.URI() + filename := uri.Filename() + goimported := string(r.data.Golden("goimports", filename, func() ([]byte, error) { + cmd := exec.Command("goimports", filename) + out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files + return out, nil + })) - actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(uri), - }, - }) - if err != nil { - if goimported != "" { - t.Error(err) - } - continue - } - m, err := r.data.Mapper(uri) - if err != nil { - t.Fatal(err) - } - var edits []protocol.TextEdit - for _, a := range actions { - if a.Title == "Organize Imports" { - edits = (*a.Edit.Changes)[string(uri)] - } - } - sedits, err := source.FromProtocolEdits(m, edits) - if err != nil { + actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + }) + if err != nil { + if goimported != "" { t.Error(err) } - got := diff.ApplyEdits(string(m.Content), sedits) - if goimported != got { - t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got) + return + } + m, err := r.data.Mapper(uri) + if err != nil { + t.Fatal(err) + } + var edits []protocol.TextEdit + for _, a := range actions { + if a.Title == "Organize Imports" { + edits = (*a.Edit.Changes)[string(uri)] } } + sedits, err := source.FromProtocolEdits(m, edits) + if err != nil { + t.Error(err) + } + got := diff.ApplyEdits(string(m.Content), sedits) + if goimported != got { + t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got) + } } -func (r *runner) SuggestedFix(t *testing.T, data tests.SuggestedFixes) { - for _, spn := range data { - uri := spn.URI() - filename := uri.Filename() - view := r.server.session.ViewOf(uri) - f, err := getGoFile(r.ctx, view, uri) - if err != nil { - t.Fatal(err) - } - diagnostics, _, err := source.Diagnostics(r.ctx, view, f, nil) - if err != nil { - t.Fatal(err) - } - actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(uri), - }, - Context: protocol.CodeActionContext{ - Only: []protocol.CodeActionKind{protocol.QuickFix}, - Diagnostics: toProtocolDiagnostics(r.ctx, diagnostics[uri]), - }, - }) - if err != nil { - t.Error(err) - continue - } - m, err := r.data.Mapper(f.URI()) - if err != nil { - t.Fatal(err) - } - var edits []protocol.TextEdit - for _, a := range actions { - if a.Title == "Remove" { - edits = (*a.Edit.Changes)[string(uri)] - } - } - sedits, err := source.FromProtocolEdits(m, edits) - if err != nil { - t.Error(err) - } - got := diff.ApplyEdits(string(m.Content), sedits) - fixed := string(r.data.Golden("suggestedfix", filename, func() ([]byte, error) { - return []byte(got), nil - })) - if fixed != got { - t.Errorf("suggested fixes failed for %s, expected:\n%v\ngot:\n%v", filename, fixed, got) +func (r *runner) SuggestedFix(t *testing.T, spn span.Span) { + uri := spn.URI() + filename := uri.Filename() + view := r.server.session.ViewOf(uri) + f, err := getGoFile(r.ctx, view, uri) + if err != nil { + t.Fatal(err) + } + diagnostics, _, err := source.Diagnostics(r.ctx, view, f, nil) + if err != nil { + t.Fatal(err) + } + actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + Context: protocol.CodeActionContext{ + Only: []protocol.CodeActionKind{protocol.QuickFix}, + Diagnostics: toProtocolDiagnostics(r.ctx, diagnostics[uri]), + }, + }) + if err != nil { + t.Error(err) + return + } + m, err := r.data.Mapper(f.URI()) + if err != nil { + t.Fatal(err) + } + var edits []protocol.TextEdit + for _, a := range actions { + if a.Title == "Remove" { + edits = (*a.Edit.Changes)[string(uri)] } } + sedits, err := source.FromProtocolEdits(m, edits) + if err != nil { + t.Error(err) + } + got := diff.ApplyEdits(string(m.Content), sedits) + fixed := string(r.data.Golden("suggestedfix", filename, func() ([]byte, error) { + return []byte(got), nil + })) + if fixed != got { + t.Errorf("suggested fixes failed for %s, expected:\n%v\ngot:\n%v", filename, fixed, got) + } } -func (r *runner) Definition(t *testing.T, data tests.Definitions) { - for _, d := range data { - sm, err := r.data.Mapper(d.Src.URI()) - if err != nil { - t.Fatal(err) - } - loc, err := sm.Location(d.Src) - if err != nil { - t.Fatalf("failed for %v: %v", d.Src, err) - } - tdpp := protocol.TextDocumentPositionParams{ - TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, - Position: loc.Range.Start, - } - var locs []protocol.Location - var hover *protocol.Hover - if d.IsType { - params := &protocol.TypeDefinitionParams{ - TextDocumentPositionParams: tdpp, - } - locs, err = r.server.TypeDefinition(r.ctx, params) - } else { - params := &protocol.DefinitionParams{ - TextDocumentPositionParams: tdpp, - } - locs, err = r.server.Definition(r.ctx, params) - if err != nil { - t.Fatalf("failed for %v: %+v", d.Src, err) - } - v := &protocol.HoverParams{ - TextDocumentPositionParams: tdpp, - } - hover, err = r.server.Hover(r.ctx, v) - } - if err != nil { - t.Fatalf("failed for %v: %v", d.Src, err) - } - if len(locs) != 1 { - t.Errorf("got %d locations for definition, expected 1", len(locs)) - } - if hover != nil { - tag := fmt.Sprintf("%s-hover", d.Name) - expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) { - return []byte(hover.Contents.Value), nil - })) - if hover.Contents.Value != expectHover { - t.Errorf("for %v got %q want %q", d.Src, hover.Contents.Value, expectHover) - } - } else if !d.OnlyHover { - locURI := span.NewURI(locs[0].URI) - lm, err := r.data.Mapper(locURI) - if err != nil { - t.Fatal(err) - } - if def, err := lm.Span(locs[0]); err != nil { - t.Fatalf("failed for %v: %v", locs[0], err) - } else if def != d.Def { - t.Errorf("for %v got %v want %v", d.Src, def, d.Def) - } - } else { - t.Errorf("no tests ran for %s", d.Src.URI()) - } +func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) { + sm, err := r.data.Mapper(d.Src.URI()) + if err != nil { + t.Fatal(err) } -} - -func (r *runner) Highlight(t *testing.T, data tests.Highlights) { - for name, locations := range data { - m, err := r.data.Mapper(locations[0].URI()) - if err != nil { - t.Fatal(err) - } - loc, err := m.Location(locations[0]) - if err != nil { - t.Fatalf("failed for %v: %v", locations[0], err) - } - tdpp := protocol.TextDocumentPositionParams{ - TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, - Position: loc.Range.Start, - } - params := &protocol.DocumentHighlightParams{ + loc, err := sm.Location(d.Src) + if err != nil { + t.Fatalf("failed for %v: %v", d.Src, err) + } + tdpp := protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + } + var locs []protocol.Location + var hover *protocol.Hover + if d.IsType { + params := &protocol.TypeDefinitionParams{ TextDocumentPositionParams: tdpp, } - highlights, err := r.server.DocumentHighlight(r.ctx, params) + locs, err = r.server.TypeDefinition(r.ctx, params) + } else { + params := &protocol.DefinitionParams{ + TextDocumentPositionParams: tdpp, + } + locs, err = r.server.Definition(r.ctx, params) + if err != nil { + t.Fatalf("failed for %v: %+v", d.Src, err) + } + v := &protocol.HoverParams{ + TextDocumentPositionParams: tdpp, + } + hover, err = r.server.Hover(r.ctx, v) + } + if err != nil { + t.Fatalf("failed for %v: %v", d.Src, err) + } + if len(locs) != 1 { + t.Errorf("got %d locations for definition, expected 1", len(locs)) + } + if hover != nil { + tag := fmt.Sprintf("%s-hover", d.Name) + expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) { + return []byte(hover.Contents.Value), nil + })) + if hover.Contents.Value != expectHover { + t.Errorf("for %v got %q want %q", d.Src, hover.Contents.Value, expectHover) + } + } else if !d.OnlyHover { + locURI := span.NewURI(locs[0].URI) + lm, err := r.data.Mapper(locURI) if err != nil { t.Fatal(err) } - if len(highlights) != len(locations) { - t.Fatalf("got %d highlights for %s, expected %d", len(highlights), name, len(locations)) + if def, err := lm.Span(locs[0]); err != nil { + t.Fatalf("failed for %v: %v", locs[0], err) + } else if def != d.Def { + t.Errorf("for %v got %v want %v", d.Src, def, d.Def) } - for i := range highlights { - if h, err := m.RangeSpan(highlights[i].Range); err != nil { - t.Fatalf("failed for %v: %v", highlights[i], err) - } else if h != locations[i] { - t.Errorf("want %v, got %v\n", locations[i], h) - } + } else { + t.Errorf("no tests ran for %s", d.Src.URI()) + } +} + +func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) { + m, err := r.data.Mapper(locations[0].URI()) + if err != nil { + t.Fatal(err) + } + loc, err := m.Location(locations[0]) + if err != nil { + t.Fatalf("failed for %v: %v", locations[0], err) + } + tdpp := protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + } + params := &protocol.DocumentHighlightParams{ + TextDocumentPositionParams: tdpp, + } + highlights, err := r.server.DocumentHighlight(r.ctx, params) + if err != nil { + t.Fatal(err) + } + if len(highlights) != len(locations) { + t.Fatalf("got %d highlights for %s, expected %d", len(highlights), name, len(locations)) + } + for i := range highlights { + if h, err := m.RangeSpan(highlights[i].Range); err != nil { + t.Fatalf("failed for %v: %v", highlights[i], err) + } else if h != locations[i] { + t.Errorf("want %v, got %v\n", locations[i], h) } } } -func (r *runner) Reference(t *testing.T, data tests.References) { - for src, itemList := range data { - sm, err := r.data.Mapper(src.URI()) +func (r *runner) Reference(t *testing.T, src span.Span, itemList []span.Span) { + sm, err := r.data.Mapper(src.URI()) + if err != nil { + t.Fatal(err) + } + loc, err := sm.Location(src) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + + want := make(map[protocol.Location]bool) + for _, pos := range itemList { + m, err := r.data.Mapper(pos.URI()) if err != nil { t.Fatal(err) } - loc, err := sm.Location(src) + loc, err := m.Location(pos) if err != nil { t.Fatalf("failed for %v: %v", src, err) } - - want := make(map[protocol.Location]bool) - for _, pos := range itemList { - m, err := r.data.Mapper(pos.URI()) - if err != nil { - t.Fatal(err) - } - loc, err := m.Location(pos) - if err != nil { - t.Fatalf("failed for %v: %v", src, err) - } - want[loc] = true - } - params := &protocol.ReferenceParams{ - TextDocumentPositionParams: protocol.TextDocumentPositionParams{ - TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, - Position: loc.Range.Start, - }, - } - got, err := r.server.References(r.ctx, params) - if err != nil { - t.Fatalf("failed for %v: %v", src, err) - } - if len(got) != len(want) { - t.Errorf("references failed: different lengths got %v want %v", len(got), len(want)) - } - for _, loc := range got { - if !want[loc] { - t.Errorf("references failed: incorrect references got %v want %v", loc, want) - } + want[loc] = true + } + params := &protocol.ReferenceParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + }, + } + got, err := r.server.References(r.ctx, params) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + if len(got) != len(want) { + t.Errorf("references failed: different lengths got %v want %v", len(got), len(want)) + } + for _, loc := range got { + if !want[loc] { + t.Errorf("references failed: incorrect references got %v want %v", loc, want) } } } -func (r *runner) Rename(t *testing.T, data tests.Renames) { - for spn, newText := range data { - tag := fmt.Sprintf("%s-rename", newText) +func (r *runner) Rename(t *testing.T, spn span.Span, newText string) { + tag := fmt.Sprintf("%s-rename", newText) - uri := spn.URI() - filename := uri.Filename() - sm, err := r.data.Mapper(uri) - if err != nil { - t.Fatal(err) - } - loc, err := sm.Location(spn) - if err != nil { - t.Fatalf("failed for %v: %v", spn, err) - } - - workspaceEdits, err := r.server.Rename(r.ctx, &protocol.RenameParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(uri), - }, - Position: loc.Range.Start, - NewName: newText, - }) - if err != nil { - renamed := string(r.data.Golden(tag, filename, func() ([]byte, error) { - return []byte(err.Error()), nil - })) - if err.Error() != renamed { - t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err) - } - continue - } - - var res []string - for uri, edits := range *workspaceEdits.Changes { - m, err := r.data.Mapper(span.URI(uri)) - if err != nil { - t.Fatal(err) - } - sedits, err := source.FromProtocolEdits(m, edits) - if err != nil { - t.Error(err) - } - filename := filepath.Base(m.URI.Filename()) - contents := applyEdits(string(m.Content), sedits) - if len(*workspaceEdits.Changes) > 1 { - contents = fmt.Sprintf("%s:\n%s", filename, contents) - } - res = append(res, contents) - } - - // Sort on filename - sort.Strings(res) - - var got string - for i, val := range res { - if i != 0 { - got += "\n" - } - got += val - } + uri := spn.URI() + filename := uri.Filename() + sm, err := r.data.Mapper(uri) + if err != nil { + t.Fatal(err) + } + loc, err := sm.Location(spn) + if err != nil { + t.Fatalf("failed for %v: %v", spn, err) + } + workspaceEdits, err := r.server.Rename(r.ctx, &protocol.RenameParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + Position: loc.Range.Start, + NewName: newText, + }) + if err != nil { renamed := string(r.data.Golden(tag, filename, func() ([]byte, error) { - return []byte(got), nil + return []byte(err.Error()), nil })) - - if renamed != got { - t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got) + if err.Error() != renamed { + t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err) } + return } -} -func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) { - for src, want := range data { - m, err := r.data.Mapper(src.URI()) + var res []string + for uri, edits := range *workspaceEdits.Changes { + m, err := r.data.Mapper(span.URI(uri)) if err != nil { t.Fatal(err) } - loc, err := m.Location(src) + sedits, err := source.FromProtocolEdits(m, edits) if err != nil { - t.Fatalf("failed for %v: %v", src, err) + t.Error(err) } - tdpp := protocol.TextDocumentPositionParams{ - TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, - Position: loc.Range.Start, + filename := filepath.Base(m.URI.Filename()) + contents := applyEdits(string(m.Content), sedits) + if len(*workspaceEdits.Changes) > 1 { + contents = fmt.Sprintf("%s:\n%s", filename, contents) } - params := &protocol.PrepareRenameParams{ - TextDocumentPositionParams: tdpp, + res = append(res, contents) + } + + // Sort on filename + sort.Strings(res) + + var got string + for i, val := range res { + if i != 0 { + got += "\n" } - got, err := r.server.PrepareRename(context.Background(), params) - if err != nil { - t.Errorf("prepare rename failed for %v: got error: %v", src, err) - continue - } - if got == nil { - if want.Text != "" { // expected an ident. - t.Errorf("prepare rename failed for %v: got nil", src) - } - continue - } - if protocol.CompareRange(*got, want.Range) != 0 { - t.Errorf("prepare rename failed: incorrect range got %v want %v", *got, want.Range) + got += val + } + + renamed := string(r.data.Golden(tag, filename, func() ([]byte, error) { + return []byte(got), nil + })) + + if renamed != got { + t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got) + } +} + +func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) { + m, err := r.data.Mapper(src.URI()) + if err != nil { + t.Fatal(err) + } + loc, err := m.Location(src) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + tdpp := protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + } + params := &protocol.PrepareRenameParams{ + TextDocumentPositionParams: tdpp, + } + got, err := r.server.PrepareRename(context.Background(), params) + if err != nil { + t.Errorf("prepare rename failed for %v: got error: %v", src, err) + return + } + if got == nil { + if want.Text != "" { // expected an ident. + t.Errorf("prepare rename failed for %v: got nil", src) } + return + } + if protocol.CompareRange(*got, want.Range) != 0 { + t.Errorf("prepare rename failed: incorrect range got %v want %v", *got, want.Range) } } @@ -640,25 +620,23 @@ func applyEdits(contents string, edits []diff.TextEdit) string { return res } -func (r *runner) Symbol(t *testing.T, data tests.Symbols) { - for uri, expectedSymbols := range data { - params := &protocol.DocumentSymbolParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: string(uri), - }, - } - symbols, err := r.server.DocumentSymbol(r.ctx, params) - if err != nil { - t.Fatal(err) - } +func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { + params := &protocol.DocumentSymbolParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: string(uri), + }, + } + symbols, err := r.server.DocumentSymbol(r.ctx, params) + if err != nil { + t.Fatal(err) + } - if len(symbols) != len(expectedSymbols) { - t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols)) - continue - } - if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" { - t.Error(diff) - } + if len(symbols) != len(expectedSymbols) { + t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols)) + return + } + if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" { + t.Error(diff) } } @@ -705,45 +683,43 @@ func summarizeSymbols(t *testing.T, i int, want, got []protocol.DocumentSymbol, return msg.String() } -func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { - for spn, expectedSignatures := range data { - m, err := r.data.Mapper(spn.URI()) - if err != nil { +func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *source.SignatureInformation) { + m, err := r.data.Mapper(spn.URI()) + if err != nil { + t.Fatal(err) + } + loc, err := m.Location(spn) + if err != nil { + t.Fatalf("failed for %v: %v", loc, err) + } + tdpp := protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(spn.URI()), + }, + Position: loc.Range.Start, + } + params := &protocol.SignatureHelpParams{ + TextDocumentPositionParams: tdpp, + } + gotSignatures, err := r.server.SignatureHelp(r.ctx, params) + if err != nil { + // Only fail if we got an error we did not expect. + if expectedSignature != nil { t.Fatal(err) } - loc, err := m.Location(spn) - if err != nil { - t.Fatalf("failed for %v: %v", loc, err) - } - tdpp := protocol.TextDocumentPositionParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(spn.URI()), - }, - Position: loc.Range.Start, - } - params := &protocol.SignatureHelpParams{ - TextDocumentPositionParams: tdpp, - } - gotSignatures, err := r.server.SignatureHelp(r.ctx, params) - if err != nil { - // Only fail if we got an error we did not expect. - if expectedSignatures != nil { - t.Fatal(err) - } - continue - } - if expectedSignatures == nil { - if gotSignatures != nil { - t.Errorf("expected no signature, got %v", gotSignatures) - } - continue - } - if gotSignatures == nil { - t.Fatalf("expected %v, got nil", expectedSignatures) - } - if diff := diffSignatures(spn, expectedSignatures, gotSignatures); diff != "" { - t.Error(diff) + return + } + if expectedSignature == nil { + if gotSignatures != nil { + t.Errorf("expected no signature, got %v", gotSignatures) } + return + } + if gotSignatures == nil { + t.Fatalf("expected %v, got nil", expectedSignature) + } + if diff := diffSignatures(spn, expectedSignature, gotSignatures); diff != "" { + t.Error(diff) } } @@ -782,56 +758,54 @@ func diffSignatures(spn span.Span, want *source.SignatureInformation, got *proto return "" } -func (r *runner) Link(t *testing.T, data tests.Links) { - for uri, wantLinks := range data { - m, err := r.data.Mapper(uri) - if err != nil { - t.Fatal(err) - } - gotLinks, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{ - TextDocument: protocol.TextDocumentIdentifier{ - URI: protocol.NewURI(uri), - }, - }) - if err != nil { - t.Fatal(err) - } - 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) - } +func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) { + m, err := r.data.Mapper(uri) + if err != nil { + t.Fatal(err) + } + gotLinks, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: protocol.NewURI(uri), + }, + }) + if err != nil { + t.Fatal(err) + } + 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 := m.RangeSpan(link.Range) - if err != nil { - t.Fatal(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 { + for _, link := range gotLinks { + spn, err := m.RangeSpan(link.Range) + if err != nil { + t.Fatal(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) - if target != link.Target { - t.Errorf("for %v want %v, got %v\n", spn, link.Target, target) - } - } else { - t.Errorf("unexpected link %v:%v\n", spn, link.Target) + linkInNote = true } } - for spn, target := range links { - t.Errorf("missing link %v:%v\n", spn, target) + if linkInNote { + continue } + if target, ok := links[spn]; ok { + delete(links, spn) + if target != link.Target { + t.Errorf("for %v want %v, got %v\n", spn, link.Target, target) + } + } else { + t.Errorf("unexpected link %v:%v\n", spn, link.Target) + } + } + for spn, target := range links { + t.Errorf("missing link %v:%v\n", spn, target) } } diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index d4058e91af..640ebae268 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -63,196 +63,178 @@ func testSource(t *testing.T, exporter packagestest.Exporter) { tests.Run(t, r, data) } -func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) { - for uri, want := range data { - f, err := r.view.GetFile(r.ctx, uri) - if err != nil { - t.Fatal(err) +func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnostic) { + f, err := r.view.GetFile(r.ctx, uri) + if err != nil { + t.Fatal(err) + } + results, _, err := source.Diagnostics(r.ctx, r.view, f.(source.GoFile), nil) + if err != nil { + t.Fatal(err) + } + got := results[uri] + // A special case to test that there are no diagnostics for a file. + if len(want) == 1 && want[0].Source == "no_diagnostics" { + if len(got) != 0 { + t.Errorf("expected no diagnostics for %s, got %v", uri, got) } - results, _, err := source.Diagnostics(r.ctx, r.view, f.(source.GoFile), nil) - if err != nil { - t.Fatal(err) - } - got := results[uri] - // A special case to test that there are no diagnostics for a file. - if len(want) == 1 && want[0].Source == "no_diagnostics" { - if len(got) != 0 { - t.Errorf("expected no diagnostics for %s, got %v", uri, got) - } + return + } + if diff := tests.DiffDiagnostics(uri, want, got); diff != "" { + t.Error(diff) + } +} + +func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + var want []protocol.CompletionItem + for _, pos := range test.CompletionItems { + want = append(want, tests.ToProtocolCompletionItem(*items[pos])) + } + prefix, list := r.callCompletion(t, src, source.CompletionOptions{ + Documentation: true, + FuzzyMatching: true, + }) + if !strings.Contains(string(src.URI()), "builtins") { + list = tests.FilterBuiltins(list) + } + var got []protocol.CompletionItem + for _, item := range list { + if !strings.HasPrefix(strings.ToLower(item.Label), prefix) { continue } - if diff := tests.DiffDiagnostics(uri, want, got); diff != "" { - t.Error(diff) - } + got = append(got, item) + } + if diff := tests.DiffCompletionItems(want, got); diff != "" { + t.Errorf("%s: %s", src, diff) } } -func (r *runner) Completion(t *testing.T, data tests.Completions, 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{ - Documentation: true, - FuzzyMatching: true, - }) - if !strings.Contains(string(src.URI()), "builtins") { - list = tests.FilterBuiltins(list) - } - var got []protocol.CompletionItem - for _, item := range list { - if !strings.HasPrefix(strings.ToLower(item.Label), prefix) { - continue - } - got = append(got, item) - } - if diff := tests.DiffCompletionItems(want, got); diff != "" { - t.Errorf("%s: %s", src, diff) - } +func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) { + _, 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) } } -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) - } - } +func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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) } } -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) +func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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 } + got = append(got, item) + } + if msg := tests.DiffCompletionItems(want, got); msg != "" { + t.Errorf("%s: %s", src, msg) } } -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 - } - got = append(got, item) - } - if msg := tests.DiffCompletionItems(want, got); msg != "" { - t.Errorf("%s: %s", src, msg) +func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + 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 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 { - 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 msg := tests.DiffCompletionItems(want, got); msg != "" { - t.Errorf("%s: %s", src, msg) - } +func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + var want []protocol.CompletionItem + for _, pos := range test.CompletionItems { + want = append(want, tests.ToProtocolCompletionItem(*items[pos])) + } + _, list := r.callCompletion(t, src, source.CompletionOptions{ + CaseSensitive: true, + }) + if !strings.Contains(string(src.URI()), "builtins") { + list = tests.FilterBuiltins(list) + } + if diff := tests.DiffCompletionItems(want, list); diff != "" { + t.Errorf("%s: %s", src, diff) } } -func (r *runner) CaseSensitiveCompletions(t *testing.T, data tests.CaseSensitiveCompletions, items tests.CompletionItems) { - for src, test := range data { - var want []protocol.CompletionItem - for _, pos := range test.CompletionItems { - want = append(want, tests.ToProtocolCompletionItem(*items[pos])) - } - _, list := r.callCompletion(t, src, source.CompletionOptions{ - CaseSensitive: true, - }) - if !strings.Contains(string(src.URI()), "builtins") { - list = tests.FilterBuiltins(list) - } - if diff := tests.DiffCompletionItems(want, list); diff != "" { - t.Errorf("%s: %s", src, diff) - } +func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { + var want []protocol.CompletionItem + for _, pos := range test.CompletionItems { + want = append(want, tests.ToProtocolCompletionItem(*items[pos])) } -} - -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) + 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) } } @@ -295,37 +277,34 @@ func (r *runner) callCompletion(t *testing.T, src span.Span, options source.Comp return prefix, tests.ToProtocolCompletionItems(items) } -func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) { - for _, spn := range data { - uri := spn.URI() - - f, err := r.view.GetFile(r.ctx, uri) - if err != nil { - t.Fatalf("failed for %v: %v", spn, err) - } - data, _, err := f.Handle(r.ctx).Read(r.ctx) - if err != nil { - t.Error(err) - continue - } - - // Test all folding ranges. - ranges, err := source.FoldingRange(r.ctx, r.view, f.(source.GoFile), false) - if err != nil { - t.Error(err) - continue - } - r.foldingRanges(t, "foldingRange", uri, string(data), ranges) - - // Test folding ranges with lineFoldingOnly - ranges, err = source.FoldingRange(r.ctx, r.view, f.(source.GoFile), true) - if err != nil { - t.Error(err) - continue - } - r.foldingRanges(t, "foldingRange-lineFolding", uri, string(data), ranges) +func (r *runner) FoldingRange(t *testing.T, spn span.Span) { + uri := spn.URI() + f, err := r.view.GetFile(r.ctx, uri) + if err != nil { + t.Fatalf("failed for %v: %v", spn, err) } + data, _, err := f.Handle(r.ctx).Read(r.ctx) + if err != nil { + t.Error(err) + return + } + + // Test all folding ranges. + ranges, err := source.FoldingRange(r.ctx, r.view, f.(source.GoFile), false) + if err != nil { + t.Error(err) + return + } + r.foldingRanges(t, "foldingRange", uri, string(data), ranges) + + // Test folding ranges with lineFoldingOnly + ranges, err = source.FoldingRange(r.ctx, r.view, f.(source.GoFile), true) + if err != nil { + t.Error(err) + return + } + r.foldingRanges(t, "foldingRange-lineFolding", uri, string(data), ranges) } func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, data string, ranges []*source.FoldingRangeInfo) { @@ -435,76 +414,252 @@ func foldRanges(contents string, ranges []*source.FoldingRangeInfo) (string, err return res, nil } -func (r *runner) Format(t *testing.T, data tests.Formats) { +func (r *runner) Format(t *testing.T, spn span.Span) { ctx := r.ctx - for _, spn := range data { - uri := spn.URI() - filename := uri.Filename() - gofmted := string(r.data.Golden("gofmt", filename, func() ([]byte, error) { - cmd := exec.Command("gofmt", filename) - out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files - return out, nil - })) - f, err := r.view.GetFile(ctx, uri) - if err != nil { - t.Fatalf("failed for %v: %v", spn, err) - } - edits, err := source.Format(ctx, r.view, f) - if err != nil { - if gofmted != "" { - t.Error(err) - } - continue - } - data, _, err := f.Handle(ctx).Read(ctx) - if err != nil { - t.Fatal(err) - } - m, err := r.data.Mapper(f.URI()) - if err != nil { - t.Fatal(err) - } - diffEdits, err := source.FromProtocolEdits(m, edits) - if err != nil { + uri := spn.URI() + filename := uri.Filename() + gofmted := string(r.data.Golden("gofmt", filename, func() ([]byte, error) { + cmd := exec.Command("gofmt", filename) + out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files + return out, nil + })) + f, err := r.view.GetFile(ctx, uri) + if err != nil { + t.Fatalf("failed for %v: %v", spn, err) + } + edits, err := source.Format(ctx, r.view, f) + if err != nil { + if gofmted != "" { t.Error(err) } - got := diff.ApplyEdits(string(data), diffEdits) - if gofmted != got { - t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got) + return + } + data, _, err := f.Handle(ctx).Read(ctx) + if err != nil { + t.Fatal(err) + } + m, err := r.data.Mapper(f.URI()) + if err != nil { + t.Fatal(err) + } + diffEdits, err := source.FromProtocolEdits(m, edits) + if err != nil { + t.Error(err) + } + got := diff.ApplyEdits(string(data), diffEdits) + if gofmted != got { + t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got) + } +} + +func (r *runner) Import(t *testing.T, spn span.Span) { + ctx := r.ctx + uri := spn.URI() + filename := uri.Filename() + goimported := string(r.data.Golden("goimports", filename, func() ([]byte, error) { + cmd := exec.Command("goimports", filename) + out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files + return out, nil + })) + f, err := r.view.GetFile(ctx, uri) + if err != nil { + t.Fatalf("failed for %v: %v", spn, err) + } + fh := f.Handle(ctx) + tok, err := r.view.Session().Cache().TokenHandle(fh).Token(ctx) + if err != nil { + t.Fatal(err) + } + rng, err := spn.Range(span.NewTokenConverter(r.data.Exported.ExpectFileSet, tok)) + if err != nil { + t.Fatalf("failed for %v: %v", spn, err) + } + edits, err := source.Imports(ctx, r.view, f.(source.GoFile), rng) + if err != nil { + if goimported != "" { + t.Error(err) + } + return + } + data, _, err := fh.Read(ctx) + if err != nil { + t.Fatal(err) + } + m, err := r.data.Mapper(fh.Identity().URI) + if err != nil { + t.Fatal(err) + } + diffEdits, err := source.FromProtocolEdits(m, edits) + if err != nil { + t.Error(err) + } + got := diff.ApplyEdits(string(data), diffEdits) + if goimported != got { + t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got) + } +} + +func (r *runner) SuggestedFix(t *testing.T, spn span.Span) { +} + +func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) { + ctx := r.ctx + f, err := r.view.GetFile(ctx, d.Src.URI()) + if err != nil { + t.Fatalf("failed for %v: %v", d.Src, err) + } + _, srcRng, err := spanToRange(r.data, d.Src) + if err != nil { + t.Fatal(err) + } + ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start) + if err != nil { + t.Fatalf("failed for %v: %v", d.Src, err) + } + h, err := ident.Hover(ctx) + if err != nil { + t.Fatalf("failed for %v: %v", d.Src, err) + } + var hover string + if h.Synopsis != "" { + hover += h.Synopsis + "\n" + } + hover += h.Signature + rng, err := ident.Range() + if err != nil { + t.Fatal(err) + } + if d.IsType { + rng, err = ident.Type.Range() + if err != nil { + t.Fatal(err) + } + hover = "" + } + if hover != "" { + tag := fmt.Sprintf("%s-hover", d.Name) + expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) { + return []byte(hover), nil + })) + if hover != expectHover { + t.Errorf("for %v got %q want %q", d.Src, hover, expectHover) + } + } else if !d.OnlyHover { + if _, defRng, err := spanToRange(r.data, d.Def); err != nil { + t.Fatal(err) + } else if rng != defRng { + t.Errorf("for %v got %v want %v", d.Src, rng, d.Def) + } + } else { + t.Errorf("no tests ran for %s", d.Src.URI()) + } +} + +func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) { + ctx := r.ctx + src := locations[0] + m, srcRng, err := spanToRange(r.data, src) + if err != nil { + t.Fatal(err) + } + highlights, err := source.Highlight(ctx, r.view, src.URI(), srcRng.Start) + if err != nil { + t.Errorf("highlight failed for %s: %v", src.URI(), err) + } + if len(highlights) != len(locations) { + t.Errorf("got %d highlights for %s, expected %d", len(highlights), name, len(locations)) + } + for i, got := range highlights { + want, err := m.Range(locations[i]) + if err != nil { + t.Fatal(err) + } + if got != want { + t.Errorf("want %v, got %v\n", want, got) } } } -func (r *runner) Import(t *testing.T, data tests.Imports) { +func (r *runner) Reference(t *testing.T, src span.Span, itemList []span.Span) { ctx := r.ctx - for _, spn := range data { - uri := spn.URI() - filename := uri.Filename() - goimported := string(r.data.Golden("goimports", filename, func() ([]byte, error) { - cmd := exec.Command("goimports", filename) - out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files - return out, nil + f, err := r.view.GetFile(ctx, src.URI()) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + _, srcRng, err := spanToRange(r.data, src) + if err != nil { + t.Fatal(err) + } + ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + + want := make(map[span.Span]bool) + for _, pos := range itemList { + want[pos] = true + } + + refs, err := ident.References(ctx) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + + got := make(map[span.Span]bool) + for _, refInfo := range refs { + refSpan, err := refInfo.Span() + if err != nil { + t.Fatal(err) + } + got[refSpan] = true + } + + if len(got) != len(want) { + t.Errorf("references failed: different lengths got %v want %v", len(got), len(want)) + } + + for spn, _ := range got { + if !want[spn] { + t.Errorf("references failed: incorrect references got %v want locations %v", got, want) + } + } +} + +func (r *runner) Rename(t *testing.T, spn span.Span, newText string) { + ctx := r.ctx + tag := fmt.Sprintf("%s-rename", newText) + + f, err := r.view.GetFile(ctx, spn.URI()) + if err != nil { + t.Fatalf("failed for %v: %v", spn, err) + } + _, srcRng, err := spanToRange(r.data, spn) + if err != nil { + t.Fatal(err) + } + ident, err := source.Identifier(r.ctx, r.view, f.(source.GoFile), srcRng.Start) + if err != nil { + t.Error(err) + return + } + changes, err := ident.Rename(r.ctx, r.view, newText) + if err != nil { + renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { + return []byte(err.Error()), nil })) - f, err := r.view.GetFile(ctx, uri) + if err.Error() != renamed { + t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err) + } + return + } + + var res []string + for editSpn, edits := range changes { + f, err := r.view.GetFile(ctx, editSpn) if err != nil { t.Fatalf("failed for %v: %v", spn, err) } fh := f.Handle(ctx) - tok, err := r.view.Session().Cache().TokenHandle(fh).Token(ctx) - if err != nil { - t.Fatal(err) - } - rng, err := spn.Range(span.NewTokenConverter(r.data.Exported.ExpectFileSet, tok)) - if err != nil { - t.Fatalf("failed for %v: %v", spn, err) - } - edits, err := source.Imports(ctx, r.view, f.(source.GoFile), rng) - if err != nil { - if goimported != "" { - t.Error(err) - } - continue - } data, _, err := fh.Read(ctx) if err != nil { t.Fatal(err) @@ -513,223 +668,35 @@ func (r *runner) Import(t *testing.T, data tests.Imports) { if err != nil { t.Fatal(err) } + filename := filepath.Base(editSpn.Filename()) diffEdits, err := source.FromProtocolEdits(m, edits) if err != nil { - t.Error(err) + t.Fatal(err) } - got := diff.ApplyEdits(string(data), diffEdits) - if goimported != got { - t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got) + contents := applyEdits(string(data), diffEdits) + if len(changes) > 1 { + contents = fmt.Sprintf("%s:\n%s", filename, contents) } + res = append(res, contents) } -} -func (r *runner) SuggestedFix(t *testing.T, data tests.SuggestedFixes) { -} + // Sort on filename + sort.Strings(res) -func (r *runner) Definition(t *testing.T, data tests.Definitions) { - ctx := r.ctx - for _, d := range data { - f, err := r.view.GetFile(ctx, d.Src.URI()) - if err != nil { - t.Fatalf("failed for %v: %v", d.Src, err) - } - _, srcRng, err := spanToRange(r.data, d.Src) - if err != nil { - t.Fatal(err) - } - ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start) - if err != nil { - t.Fatalf("failed for %v: %v", d.Src, err) - } - h, err := ident.Hover(ctx) - if err != nil { - t.Fatalf("failed for %v: %v", d.Src, err) - } - var hover string - if h.Synopsis != "" { - hover += h.Synopsis + "\n" - } - hover += h.Signature - rng, err := ident.Range() - if err != nil { - t.Fatal(err) - } - if d.IsType { - rng, err = ident.Type.Range() - if err != nil { - t.Fatal(err) - } - hover = "" - } - if hover != "" { - tag := fmt.Sprintf("%s-hover", d.Name) - expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) { - return []byte(hover), nil - })) - if hover != expectHover { - t.Errorf("for %v got %q want %q", d.Src, hover, expectHover) - } - } else if !d.OnlyHover { - if _, defRng, err := spanToRange(r.data, d.Def); err != nil { - t.Fatal(err) - } else if rng != defRng { - t.Errorf("for %v got %v want %v", d.Src, rng, d.Def) - } - } else { - t.Errorf("no tests ran for %s", d.Src.URI()) + var got string + for i, val := range res { + if i != 0 { + got += "\n" } + got += val } -} -func (r *runner) Highlight(t *testing.T, data tests.Highlights) { - ctx := r.ctx - for name, locations := range data { - src := locations[0] - m, srcRng, err := spanToRange(r.data, src) - if err != nil { - t.Fatal(err) - } - highlights, err := source.Highlight(ctx, r.view, src.URI(), srcRng.Start) - if err != nil { - t.Errorf("highlight failed for %s: %v", src.URI(), err) - } - if len(highlights) != len(locations) { - t.Errorf("got %d highlights for %s, expected %d", len(highlights), name, len(locations)) - } - for i, got := range highlights { - want, err := m.Range(locations[i]) - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("want %v, got %v\n", want, got) - } - } - } -} + renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { + return []byte(got), nil + })) -func (r *runner) Reference(t *testing.T, data tests.References) { - ctx := r.ctx - for src, itemList := range data { - f, err := r.view.GetFile(ctx, src.URI()) - if err != nil { - t.Fatalf("failed for %v: %v", src, err) - } - _, srcRng, err := spanToRange(r.data, src) - if err != nil { - t.Fatal(err) - } - ident, err := source.Identifier(ctx, r.view, f.(source.GoFile), srcRng.Start) - if err != nil { - t.Fatalf("failed for %v: %v", src, err) - } - - want := make(map[span.Span]bool) - for _, pos := range itemList { - want[pos] = true - } - - refs, err := ident.References(ctx) - if err != nil { - t.Fatalf("failed for %v: %v", src, err) - } - - got := make(map[span.Span]bool) - for _, refInfo := range refs { - refSpan, err := refInfo.Span() - if err != nil { - t.Fatal(err) - } - got[refSpan] = true - } - - if len(got) != len(want) { - t.Errorf("references failed: different lengths got %v want %v", len(got), len(want)) - } - - for spn, _ := range got { - if !want[spn] { - t.Errorf("references failed: incorrect references got %v want locations %v", got, want) - } - } - } -} - -func (r *runner) Rename(t *testing.T, data tests.Renames) { - ctx := r.ctx - for spn, newText := range data { - tag := fmt.Sprintf("%s-rename", newText) - - f, err := r.view.GetFile(ctx, spn.URI()) - if err != nil { - t.Fatalf("failed for %v: %v", spn, err) - } - _, srcRng, err := spanToRange(r.data, spn) - if err != nil { - t.Fatal(err) - } - ident, err := source.Identifier(r.ctx, r.view, f.(source.GoFile), srcRng.Start) - if err != nil { - t.Error(err) - continue - } - changes, err := ident.Rename(r.ctx, r.view, newText) - if err != nil { - renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { - return []byte(err.Error()), nil - })) - if err.Error() != renamed { - t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err) - } - continue - } - - var res []string - for editSpn, edits := range changes { - f, err := r.view.GetFile(ctx, editSpn) - if err != nil { - t.Fatalf("failed for %v: %v", spn, err) - } - fh := f.Handle(ctx) - data, _, err := fh.Read(ctx) - if err != nil { - t.Fatal(err) - } - m, err := r.data.Mapper(fh.Identity().URI) - if err != nil { - t.Fatal(err) - } - filename := filepath.Base(editSpn.Filename()) - diffEdits, err := source.FromProtocolEdits(m, edits) - if err != nil { - t.Fatal(err) - } - contents := applyEdits(string(data), diffEdits) - if len(changes) > 1 { - contents = fmt.Sprintf("%s:\n%s", filename, contents) - } - res = append(res, contents) - } - - // Sort on filename - sort.Strings(res) - - var got string - for i, val := range res { - if i != 0 { - got += "\n" - } - got += val - } - - renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { - return []byte(got), nil - })) - - if renamed != got { - t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got) - } + if renamed != got { + t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got) } } @@ -748,59 +715,55 @@ func applyEdits(contents string, edits []diff.TextEdit) string { return res } -func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) { +func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) { ctx := context.Background() - for src, want := range data { - f, err := r.view.GetFile(ctx, src.URI()) - if err != nil { - t.Fatalf("failed for %v: %v", src, err) + f, err := r.view.GetFile(ctx, src.URI()) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + _, srcRng, err := spanToRange(r.data, src) + if err != nil { + t.Fatal(err) + } + // Find the identifier at the position. + item, err := source.PrepareRename(ctx, r.view, f.(source.GoFile), srcRng.Start) + if err != nil { + if want.Text != "" { // expected an ident. + t.Errorf("prepare rename failed for %v: got error: %v", src, err) } - _, srcRng, err := spanToRange(r.data, src) - if err != nil { - t.Fatal(err) - } - // Find the identifier at the position. - item, err := source.PrepareRename(ctx, r.view, f.(source.GoFile), srcRng.Start) - if err != nil { - if want.Text != "" { // expected an ident. - t.Errorf("prepare rename failed for %v: got error: %v", src, err) - } - continue - } - if item == nil { - if want.Text != "" { - t.Errorf("prepare rename failed for %v: got nil", src) - } - continue - } - if want.Text == "" && item != nil { - t.Errorf("prepare rename failed for %v: expected nil, got %v", src, item) - continue - } - if protocol.CompareRange(want.Range, item.Range) != 0 { - t.Errorf("prepare rename failed: incorrect range got %v want %v", item.Range, want.Range) + return + } + if item == nil { + if want.Text != "" { + t.Errorf("prepare rename failed for %v: got nil", src) } + return + } + if want.Text == "" && item != nil { + t.Errorf("prepare rename failed for %v: expected nil, got %v", src, item) + return + } + if protocol.CompareRange(want.Range, item.Range) != 0 { + t.Errorf("prepare rename failed: incorrect range got %v want %v", item.Range, want.Range) } } -func (r *runner) Symbol(t *testing.T, data tests.Symbols) { +func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { ctx := r.ctx - for uri, expectedSymbols := range data { - f, err := r.view.GetFile(ctx, uri) - if err != nil { - t.Fatalf("failed for %v: %v", uri, err) - } - symbols, err := source.DocumentSymbols(ctx, r.view, f.(source.GoFile)) - if err != nil { - t.Errorf("symbols failed for %s: %v", uri, err) - } - if len(symbols) != len(expectedSymbols) { - t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols)) - continue - } - if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" { - t.Error(diff) - } + f, err := r.view.GetFile(ctx, uri) + if err != nil { + t.Fatalf("failed for %v: %v", uri, err) + } + symbols, err := source.DocumentSymbols(ctx, r.view, f.(source.GoFile)) + if err != nil { + t.Errorf("symbols failed for %s: %v", uri, err) + } + if len(symbols) != len(expectedSymbols) { + t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols)) + return + } + if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" { + t.Error(diff) } } @@ -847,33 +810,31 @@ func summarizeSymbols(t *testing.T, i int, want, got []protocol.DocumentSymbol, return msg.String() } -func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { +func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *source.SignatureInformation) { ctx := r.ctx - for spn, expectedSignature := range data { - f, err := r.view.GetFile(ctx, spn.URI()) - if err != nil { + f, err := r.view.GetFile(ctx, spn.URI()) + if err != nil { + t.Fatalf("failed for %v: %v", spn, err) + } + _, rng, err := spanToRange(r.data, spn) + if err != nil { + t.Fatal(err) + } + gotSignature, err := source.SignatureHelp(ctx, r.view, f.(source.GoFile), rng.Start) + if err != nil { + // Only fail if we got an error we did not expect. + if expectedSignature != nil { t.Fatalf("failed for %v: %v", spn, err) } - _, rng, err := spanToRange(r.data, spn) - if err != nil { - t.Fatal(err) - } - gotSignature, err := source.SignatureHelp(ctx, r.view, f.(source.GoFile), rng.Start) - if err != nil { - // Only fail if we got an error we did not expect. - if expectedSignature != nil { - t.Fatalf("failed for %v: %v", spn, err) - } - } - if expectedSignature == nil { - if gotSignature != nil { - t.Errorf("expected no signature, got %v", gotSignature) - } - continue - } - if diff := diffSignatures(spn, expectedSignature, gotSignature); diff != "" { - t.Error(diff) + } + if expectedSignature == nil { + if gotSignature != nil { + t.Errorf("expected no signature, got %v", gotSignature) } + return + } + if diff := diffSignatures(spn, expectedSignature, gotSignature); diff != "" { + t.Error(diff) } } @@ -898,7 +859,7 @@ func diffSignatures(spn span.Span, want *source.SignatureInformation, got *sourc return "" } -func (r *runner) Link(t *testing.T, data tests.Links) { +func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) { // This is a pure LSP feature, no source level functionality to be tested. } diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index c98ed6f4c8..30cfacc1a3 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -96,26 +96,26 @@ type Data struct { } type Tests interface { - Diagnostics(*testing.T, Diagnostics) - 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) - CaseSensitiveCompletions(*testing.T, CaseSensitiveCompletions, CompletionItems) - RankCompletions(*testing.T, RankCompletions, CompletionItems) - FoldingRange(*testing.T, FoldingRanges) - Format(*testing.T, Formats) - Import(*testing.T, Imports) - SuggestedFix(*testing.T, SuggestedFixes) - Definition(*testing.T, Definitions) - Highlight(*testing.T, Highlights) - Reference(*testing.T, References) - Rename(*testing.T, Renames) - PrepareRename(*testing.T, PrepareRenames) - Symbol(*testing.T, Symbols) - SignatureHelp(*testing.T, Signatures) - Link(*testing.T, Links) + Diagnostics(*testing.T, span.URI, []source.Diagnostic) + Completion(*testing.T, span.Span, Completion, CompletionItems) + CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems) + UnimportedCompletion(*testing.T, span.Span, Completion, CompletionItems) + DeepCompletion(*testing.T, span.Span, Completion, CompletionItems) + FuzzyCompletion(*testing.T, span.Span, Completion, CompletionItems) + CaseSensitiveCompletion(*testing.T, span.Span, Completion, CompletionItems) + RankCompletion(*testing.T, span.Span, Completion, CompletionItems) + FoldingRange(*testing.T, span.Span) + Format(*testing.T, span.Span) + Import(*testing.T, span.Span) + SuggestedFix(*testing.T, span.Span) + Definition(*testing.T, span.Span, Definition) + Highlight(*testing.T, string, []span.Span) + Reference(*testing.T, span.Span, []span.Span) + Rename(*testing.T, span.Span, string) + PrepareRename(*testing.T, span.Span, *source.PrepareItem) + Symbol(*testing.T, span.URI, []protocol.DocumentSymbol) + SignatureHelp(*testing.T, span.Span, *source.SignatureInformation) + Link(*testing.T, span.URI, []Link) } type Definition struct { @@ -327,102 +327,144 @@ func Run(t *testing.T, tests Tests, data *Data) { t.Run("Completion", func(t *testing.T) { t.Helper() - tests.Completion(t, data.Completions, data.CompletionItems) + for src, test := range data.Completions { + tests.Completion(t, src, test, data.CompletionItems) + } }) t.Run("CompletionSnippets", func(t *testing.T) { t.Helper() - tests.CompletionSnippets(t, data.CompletionSnippets, data.CompletionItems) + for _, placeholders := range []bool{true, false} { + for src, expected := range data.CompletionSnippets { + tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems) + } + } }) t.Run("UnimportedCompletion", func(t *testing.T) { t.Helper() - tests.UnimportedCompletions(t, data.UnimportedCompletions, data.CompletionItems) + for src, test := range data.UnimportedCompletions { + tests.UnimportedCompletion(t, src, test, data.CompletionItems) + } }) t.Run("DeepCompletion", func(t *testing.T) { t.Helper() - tests.DeepCompletions(t, data.DeepCompletions, data.CompletionItems) + for src, test := range data.DeepCompletions { + tests.DeepCompletion(t, src, test, data.CompletionItems) + } }) t.Run("FuzzyCompletion", func(t *testing.T) { t.Helper() - tests.FuzzyCompletions(t, data.FuzzyCompletions, data.CompletionItems) + for src, test := range data.FuzzyCompletions { + tests.FuzzyCompletion(t, src, test, data.CompletionItems) + } }) t.Run("CaseSensitiveCompletion", func(t *testing.T) { t.Helper() - tests.CaseSensitiveCompletions(t, data.CaseSensitiveCompletions, data.CompletionItems) + for src, test := range data.CaseSensitiveCompletions { + tests.CaseSensitiveCompletion(t, src, test, data.CompletionItems) + } }) t.Run("RankCompletions", func(t *testing.T) { t.Helper() - tests.RankCompletions(t, data.RankCompletions, data.CompletionItems) + for src, test := range data.RankCompletions { + tests.RankCompletion(t, src, test, data.CompletionItems) + } }) t.Run("Diagnostics", func(t *testing.T) { t.Helper() - tests.Diagnostics(t, data.Diagnostics) + for uri, want := range data.Diagnostics { + tests.Diagnostics(t, uri, want) + } }) t.Run("FoldingRange", func(t *testing.T) { t.Helper() - tests.FoldingRange(t, data.FoldingRanges) + for _, spn := range data.FoldingRanges { + tests.FoldingRange(t, spn) + } }) t.Run("Format", func(t *testing.T) { t.Helper() - tests.Format(t, data.Formats) + for _, spn := range data.Formats { + tests.Format(t, spn) + } }) t.Run("Import", func(t *testing.T) { t.Helper() - tests.Import(t, data.Imports) + for _, spn := range data.Imports { + tests.Import(t, spn) + } }) t.Run("SuggestedFix", func(t *testing.T) { t.Helper() - tests.SuggestedFix(t, data.SuggestedFixes) + for _, spn := range data.SuggestedFixes { + tests.SuggestedFix(t, spn) + } }) t.Run("Definition", func(t *testing.T) { t.Helper() - tests.Definition(t, data.Definitions) + for spn, d := range data.Definitions { + tests.Definition(t, spn, d) + } }) t.Run("Highlight", func(t *testing.T) { t.Helper() - tests.Highlight(t, data.Highlights) + for name, locations := range data.Highlights { + tests.Highlight(t, name, locations) + } }) t.Run("References", func(t *testing.T) { t.Helper() - tests.Reference(t, data.References) + for src, itemList := range data.References { + tests.Reference(t, src, itemList) + } }) t.Run("Renames", func(t *testing.T) { t.Helper() - tests.Rename(t, data.Renames) + for spn, newText := range data.Renames { + tests.Rename(t, spn, newText) + } }) t.Run("PrepareRenames", func(t *testing.T) { t.Helper() - tests.PrepareRename(t, data.PrepareRenames) + for src, want := range data.PrepareRenames { + tests.PrepareRename(t, src, want) + } }) t.Run("Symbols", func(t *testing.T) { t.Helper() - tests.Symbol(t, data.Symbols) + for uri, expectedSymbols := range data.Symbols { + tests.Symbol(t, uri, expectedSymbols) + } }) t.Run("SignatureHelp", func(t *testing.T) { t.Helper() - tests.SignatureHelp(t, data.Signatures) + for spn, expectedSignature := range data.Signatures { + tests.SignatureHelp(t, spn, expectedSignature) + } }) t.Run("Link", func(t *testing.T) { t.Helper() - tests.Link(t, data.Links) + for uri, wantLinks := range data.Links { + tests.Link(t, uri, wantLinks) + } }) if *UpdateGolden {