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 {