1 // Copyright 2019 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package tests exports functionality to be used across a variety of gopls tests.
26 "golang.org/x/tools/go/expect"
27 "golang.org/x/tools/go/packages"
28 "golang.org/x/tools/go/packages/packagestest"
29 "golang.org/x/tools/internal/lsp/command"
30 "golang.org/x/tools/internal/lsp/protocol"
31 "golang.org/x/tools/internal/lsp/source"
32 "golang.org/x/tools/internal/lsp/source/completion"
33 "golang.org/x/tools/internal/span"
34 "golang.org/x/tools/internal/testenv"
35 "golang.org/x/tools/txtar"
39 overlayFileSuffix = ".overlay"
40 goldenFileSuffix = ".golden"
42 summaryFile = "summary.txt"
43 testModule = "golang.org/x/tools/internal/lsp"
46 var UpdateGolden = flag.Bool("golden", false, "Update golden files")
48 type CallHierarchy map[span.Span]*CallHierarchyResult
49 type CodeLens map[span.URI][]protocol.CodeLens
50 type Diagnostics map[span.URI][]*source.Diagnostic
51 type CompletionItems map[token.Pos]*completion.CompletionItem
52 type Completions map[span.Span][]Completion
53 type CompletionSnippets map[span.Span][]CompletionSnippet
54 type UnimportedCompletions map[span.Span][]Completion
55 type DeepCompletions map[span.Span][]Completion
56 type FuzzyCompletions map[span.Span][]Completion
57 type CaseSensitiveCompletions map[span.Span][]Completion
58 type RankCompletions map[span.Span][]Completion
59 type FoldingRanges []span.Span
60 type Formats []span.Span
61 type Imports []span.Span
62 type SemanticTokens []span.Span
63 type SuggestedFixes map[span.Span][]string
64 type FunctionExtractions map[span.Span]span.Span
65 type Definitions map[span.Span]Definition
66 type Implementations map[span.Span][]span.Span
67 type Highlights map[span.Span][]span.Span
68 type References map[span.Span][]span.Span
69 type Renames map[span.Span]string
70 type PrepareRenames map[span.Span]*source.PrepareItem
71 type Symbols map[span.URI][]protocol.DocumentSymbol
72 type SymbolsChildren map[string][]protocol.DocumentSymbol
73 type SymbolInformation map[span.Span]protocol.SymbolInformation
74 type WorkspaceSymbols map[WorkspaceSymbolsTestType]map[span.URI][]string
75 type Signatures map[span.Span]*protocol.SignatureHelp
76 type Links map[span.URI][]Link
79 Config packages.Config
80 Exported *packagestest.Exported
81 CallHierarchy CallHierarchy
83 Diagnostics Diagnostics
84 CompletionItems CompletionItems
85 Completions Completions
86 CompletionSnippets CompletionSnippets
87 UnimportedCompletions UnimportedCompletions
88 DeepCompletions DeepCompletions
89 FuzzyCompletions FuzzyCompletions
90 CaseSensitiveCompletions CaseSensitiveCompletions
91 RankCompletions RankCompletions
92 FoldingRanges FoldingRanges
95 SemanticTokens SemanticTokens
96 SuggestedFixes SuggestedFixes
97 FunctionExtractions FunctionExtractions
98 Definitions Definitions
99 Implementations Implementations
100 Highlights Highlights
101 References References
103 PrepareRenames PrepareRenames
105 symbolsChildren SymbolsChildren
106 symbolInformation SymbolInformation
107 WorkspaceSymbols WorkspaceSymbols
108 Signatures Signatures
112 fragments map[string]string
114 golden map[string]*Golden
117 ModfileFlagAvailable bool
120 mappers map[span.URI]*protocol.ColumnMapper
123 type Tests interface {
124 CallHierarchy(*testing.T, span.Span, *CallHierarchyResult)
125 CodeLens(*testing.T, span.URI, []protocol.CodeLens)
126 Diagnostics(*testing.T, span.URI, []*source.Diagnostic)
127 Completion(*testing.T, span.Span, Completion, CompletionItems)
128 CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems)
129 UnimportedCompletion(*testing.T, span.Span, Completion, CompletionItems)
130 DeepCompletion(*testing.T, span.Span, Completion, CompletionItems)
131 FuzzyCompletion(*testing.T, span.Span, Completion, CompletionItems)
132 CaseSensitiveCompletion(*testing.T, span.Span, Completion, CompletionItems)
133 RankCompletion(*testing.T, span.Span, Completion, CompletionItems)
134 FoldingRanges(*testing.T, span.Span)
135 Format(*testing.T, span.Span)
136 Import(*testing.T, span.Span)
137 SemanticTokens(*testing.T, span.Span)
138 SuggestedFix(*testing.T, span.Span, []string, int)
139 FunctionExtraction(*testing.T, span.Span, span.Span)
140 Definition(*testing.T, span.Span, Definition)
141 Implementation(*testing.T, span.Span, []span.Span)
142 Highlight(*testing.T, span.Span, []span.Span)
143 References(*testing.T, span.Span, []span.Span)
144 Rename(*testing.T, span.Span, string)
145 PrepareRename(*testing.T, span.Span, *source.PrepareItem)
146 Symbols(*testing.T, span.URI, []protocol.DocumentSymbol)
147 WorkspaceSymbols(*testing.T, span.URI, string, WorkspaceSymbolsTestType)
148 SignatureHelp(*testing.T, span.Span, *protocol.SignatureHelp)
149 Link(*testing.T, span.URI, []Link)
152 type Definition struct {
159 type CompletionTestType int
162 // Default runs the standard completion tests.
163 CompletionDefault = CompletionTestType(iota)
165 // Unimported tests the autocompletion of unimported packages.
168 // Deep tests deep completion.
171 // Fuzzy tests deep completion and fuzzy matching.
174 // CaseSensitive tests case sensitive completion.
175 CompletionCaseSensitive
177 // CompletionRank candidates in test must be valid and in the right relative order.
181 type WorkspaceSymbolsTestType int
184 // Default runs the standard workspace symbols tests.
185 WorkspaceSymbolsDefault = WorkspaceSymbolsTestType(iota)
187 // Fuzzy tests workspace symbols with fuzzy matching.
188 WorkspaceSymbolsFuzzy
190 // CaseSensitive tests workspace symbols with case sensitive.
191 WorkspaceSymbolsCaseSensitive
194 type Completion struct {
195 CompletionItems []token.Pos
198 type CompletionSnippet struct {
199 CompletionItem token.Pos
201 PlaceholderSnippet string
204 type CallHierarchyResult struct {
205 IncomingCalls, OutgoingCalls []protocol.CallHierarchyItem
211 NotePosition token.Position
216 Archive *txtar.Archive
220 func Context(t testing.TB) context.Context {
221 return context.Background()
224 func DefaultOptions(o *source.Options) {
225 o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
227 protocol.SourceOrganizeImports: true,
228 protocol.QuickFix: true,
229 protocol.RefactorRewrite: true,
230 protocol.RefactorExtract: true,
231 protocol.SourceFixAll: true,
234 protocol.SourceOrganizeImports: true,
238 o.UserOptions.Codelenses[string(command.Test)] = true
239 o.HoverKind = source.SynopsisDocumentation
240 o.InsertTextFormat = protocol.SnippetTextFormat
241 o.CompletionBudget = time.Minute
242 o.HierarchicalDocumentSymbolSupport = true
243 o.ExperimentalWorkspaceModule = true
244 o.SemanticTokens = true
247 func RunTests(t *testing.T, dataDir string, includeMultiModule bool, f func(*testing.T, *Data)) {
249 modes := []string{"Modules", "GOPATH"}
250 if includeMultiModule {
251 modes = append(modes, "MultiModule")
253 for _, mode := range modes {
254 t.Run(mode, func(t *testing.T) {
256 if mode == "MultiModule" {
257 // Some bug in 1.12 breaks reading markers, and it's not worth figuring out.
258 testenv.NeedsGo1Point(t, 13)
260 datum := load(t, mode, dataDir)
266 func load(t testing.TB, mode string, dir string) *Data {
270 CallHierarchy: make(CallHierarchy),
271 CodeLens: make(CodeLens),
272 Diagnostics: make(Diagnostics),
273 CompletionItems: make(CompletionItems),
274 Completions: make(Completions),
275 CompletionSnippets: make(CompletionSnippets),
276 UnimportedCompletions: make(UnimportedCompletions),
277 DeepCompletions: make(DeepCompletions),
278 FuzzyCompletions: make(FuzzyCompletions),
279 RankCompletions: make(RankCompletions),
280 CaseSensitiveCompletions: make(CaseSensitiveCompletions),
281 Definitions: make(Definitions),
282 Implementations: make(Implementations),
283 Highlights: make(Highlights),
284 References: make(References),
285 Renames: make(Renames),
286 PrepareRenames: make(PrepareRenames),
287 SuggestedFixes: make(SuggestedFixes),
288 FunctionExtractions: make(FunctionExtractions),
289 Symbols: make(Symbols),
290 symbolsChildren: make(SymbolsChildren),
291 symbolInformation: make(SymbolInformation),
292 WorkspaceSymbols: make(WorkspaceSymbols),
293 Signatures: make(Signatures),
298 fragments: map[string]string{},
299 golden: map[string]*Golden{},
301 mappers: map[span.URI]*protocol.ColumnMapper{},
305 summary := filepath.Join(filepath.FromSlash(dir), summaryFile+goldenFileSuffix)
306 if _, err := os.Stat(summary); os.IsNotExist(err) {
307 t.Fatalf("could not find golden file summary.txt in %#v", dir)
309 archive, err := txtar.ParseFile(summary)
311 t.Fatalf("could not read golden file %v/%v: %v", dir, summary, err)
313 datum.golden[summaryFile] = &Golden{
319 files := packagestest.MustCopyFileTree(dir)
320 overlays := map[string][]byte{}
321 for fragment, operation := range files {
322 if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {
323 delete(files, fragment)
324 goldFile := filepath.Join(dir, fragment)
325 archive, err := txtar.ParseFile(goldFile)
327 t.Fatalf("could not read golden file %v: %v", fragment, err)
329 datum.golden[trimmed] = &Golden{
333 } else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment {
334 delete(files, fragment)
335 files[trimmed] = operation
336 } else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 {
337 delete(files, fragment)
338 partial := fragment[:index] + fragment[index+len(overlayFileSuffix):]
339 contents, err := ioutil.ReadFile(filepath.Join(dir, fragment))
343 overlays[partial] = contents
347 modules := []packagestest.Module{
356 datum.Exported = packagestest.Export(t, packagestest.Modules, modules)
358 datum.Exported = packagestest.Export(t, packagestest.GOPATH, modules)
360 files := map[string]interface{}{}
361 for k, v := range modules[0].Files {
362 files[filepath.Join("testmodule", k)] = v
364 modules[0].Files = files
366 overlays := map[string][]byte{}
367 for k, v := range modules[0].Overlay {
368 overlays[filepath.Join("testmodule", k)] = v
370 modules[0].Overlay = overlays
372 golden := map[string]*Golden{}
373 for k, v := range datum.golden {
374 if k == summaryFile {
377 golden[filepath.Join("testmodule", k)] = v
380 datum.golden = golden
382 datum.Exported = packagestest.Export(t, packagestest.Modules, modules)
384 panic("unknown mode " + mode)
387 for _, m := range modules {
388 for fragment := range m.Files {
389 filename := datum.Exported.File(m.Name, fragment)
390 datum.fragments[filename] = fragment
394 // Turn off go/packages debug logging.
395 datum.Exported.Config.Logf = nil
396 datum.Config.Logf = nil
398 // Merge the exported.Config with the view.Config.
399 datum.Config = *datum.Exported.Config
400 datum.Config.Fset = token.NewFileSet()
401 datum.Config.Context = Context(nil)
402 datum.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
403 panic("ParseFile should not be called")
406 // Do a first pass to collect special markers for completion and workspace symbols.
407 if err := datum.Exported.Expect(map[string]interface{}{
408 "item": func(name string, r packagestest.Range, _ []string) {
409 datum.Exported.Mark(name, r)
411 "symbol": func(name string, r packagestest.Range, _ []string) {
412 datum.Exported.Mark(name, r)
418 // Collect any data that needs to be used by subsequent tests.
419 if err := datum.Exported.Expect(map[string]interface{}{
420 "codelens": datum.collectCodeLens,
421 "diag": datum.collectDiagnostics,
422 "item": datum.collectCompletionItems,
423 "complete": datum.collectCompletions(CompletionDefault),
424 "unimported": datum.collectCompletions(CompletionUnimported),
425 "deep": datum.collectCompletions(CompletionDeep),
426 "fuzzy": datum.collectCompletions(CompletionFuzzy),
427 "casesensitive": datum.collectCompletions(CompletionCaseSensitive),
428 "rank": datum.collectCompletions(CompletionRank),
429 "snippet": datum.collectCompletionSnippets,
430 "fold": datum.collectFoldingRanges,
431 "format": datum.collectFormats,
432 "import": datum.collectImports,
433 "semantic": datum.collectSemanticTokens,
434 "godef": datum.collectDefinitions,
435 "implementations": datum.collectImplementations,
436 "typdef": datum.collectTypeDefinitions,
437 "hover": datum.collectHoverDefinitions,
438 "highlight": datum.collectHighlights,
439 "refs": datum.collectReferences,
440 "rename": datum.collectRenames,
441 "prepare": datum.collectPrepareRenames,
442 "symbol": datum.collectSymbols,
443 "signature": datum.collectSignatures,
444 "link": datum.collectLinks,
445 "suggestedfix": datum.collectSuggestedFixes,
446 "extractfunc": datum.collectFunctionExtractions,
447 "incomingcalls": datum.collectIncomingCalls,
448 "outgoingcalls": datum.collectOutgoingCalls,
452 for _, symbols := range datum.Symbols {
453 for i := range symbols {
454 children := datum.symbolsChildren[symbols[i].Name]
455 symbols[i].Children = children
458 // Collect names for the entries that require golden files.
459 if err := datum.Exported.Expect(map[string]interface{}{
460 "godef": datum.collectDefinitionNames,
461 "hover": datum.collectDefinitionNames,
462 "workspacesymbol": datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault),
463 "workspacesymbolfuzzy": datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy),
464 "workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive),
468 if mode == "MultiModule" {
469 if err := os.Rename(filepath.Join(datum.Config.Dir, "go.mod"), filepath.Join(datum.Config.Dir, "testmodule/go.mod")); err != nil {
477 func Run(t *testing.T, tests Tests, data *Data) {
481 eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) {
484 for src, exp := range cases {
485 for i, e := range exp {
486 t.Run(SpanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) {
488 if strings.Contains(t.Name(), "cgo") {
489 testenv.NeedsTool(t, "cgo")
491 if strings.Contains(t.Name(), "declarecgo") {
492 testenv.NeedsGo1Point(t, 15)
494 test(t, src, e, data.CompletionItems)
501 t.Run("CallHierarchy", func(t *testing.T) {
503 for spn, callHierarchyResult := range data.CallHierarchy {
504 t.Run(SpanName(spn), func(t *testing.T) {
506 tests.CallHierarchy(t, spn, callHierarchyResult)
511 t.Run("Completion", func(t *testing.T) {
513 eachCompletion(t, data.Completions, tests.Completion)
516 t.Run("CompletionSnippets", func(t *testing.T) {
518 for _, placeholders := range []bool{true, false} {
519 for src, expecteds := range data.CompletionSnippets {
520 for i, expected := range expecteds {
521 name := SpanName(src) + "_" + strconv.Itoa(i+1)
523 name += "_placeholders"
526 t.Run(name, func(t *testing.T) {
528 tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems)
535 t.Run("UnimportedCompletion", func(t *testing.T) {
537 eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion)
540 t.Run("DeepCompletion", func(t *testing.T) {
542 eachCompletion(t, data.DeepCompletions, tests.DeepCompletion)
545 t.Run("FuzzyCompletion", func(t *testing.T) {
547 eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion)
550 t.Run("CaseSensitiveCompletion", func(t *testing.T) {
552 eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion)
555 t.Run("RankCompletions", func(t *testing.T) {
557 eachCompletion(t, data.RankCompletions, tests.RankCompletion)
560 t.Run("CodeLens", func(t *testing.T) {
562 for uri, want := range data.CodeLens {
563 // Check if we should skip this URI if the -modfile flag is not available.
564 if shouldSkip(data, uri) {
567 t.Run(uriName(uri), func(t *testing.T) {
569 tests.CodeLens(t, uri, want)
574 t.Run("Diagnostics", func(t *testing.T) {
576 for uri, want := range data.Diagnostics {
577 // Check if we should skip this URI if the -modfile flag is not available.
578 if shouldSkip(data, uri) {
581 t.Run(uriName(uri), func(t *testing.T) {
583 tests.Diagnostics(t, uri, want)
588 t.Run("FoldingRange", func(t *testing.T) {
590 for _, spn := range data.FoldingRanges {
591 t.Run(uriName(spn.URI()), func(t *testing.T) {
593 tests.FoldingRanges(t, spn)
598 t.Run("Format", func(t *testing.T) {
600 for _, spn := range data.Formats {
601 t.Run(uriName(spn.URI()), func(t *testing.T) {
608 t.Run("Import", func(t *testing.T) {
610 for _, spn := range data.Imports {
611 t.Run(uriName(spn.URI()), func(t *testing.T) {
618 t.Run("SemanticTokens", func(t *testing.T) {
620 for _, spn := range data.SemanticTokens {
621 t.Run(uriName(spn.URI()), func(t *testing.T) {
623 tests.SemanticTokens(t, spn)
628 t.Run("SuggestedFix", func(t *testing.T) {
630 for spn, actionKinds := range data.SuggestedFixes {
631 // Check if we should skip this spn if the -modfile flag is not available.
632 if shouldSkip(data, spn.URI()) {
635 t.Run(SpanName(spn), func(t *testing.T) {
637 tests.SuggestedFix(t, spn, actionKinds, 1)
642 t.Run("FunctionExtraction", func(t *testing.T) {
644 for start, end := range data.FunctionExtractions {
645 // Check if we should skip this spn if the -modfile flag is not available.
646 if shouldSkip(data, start.URI()) {
649 t.Run(SpanName(start), func(t *testing.T) {
651 tests.FunctionExtraction(t, start, end)
656 t.Run("Definition", func(t *testing.T) {
658 for spn, d := range data.Definitions {
659 t.Run(SpanName(spn), func(t *testing.T) {
661 if strings.Contains(t.Name(), "cgo") {
662 testenv.NeedsTool(t, "cgo")
664 if strings.Contains(t.Name(), "declarecgo") {
665 testenv.NeedsGo1Point(t, 15)
667 tests.Definition(t, spn, d)
672 t.Run("Implementation", func(t *testing.T) {
674 for spn, m := range data.Implementations {
675 t.Run(SpanName(spn), func(t *testing.T) {
677 tests.Implementation(t, spn, m)
682 t.Run("Highlight", func(t *testing.T) {
684 for pos, locations := range data.Highlights {
685 t.Run(SpanName(pos), func(t *testing.T) {
687 tests.Highlight(t, pos, locations)
692 t.Run("References", func(t *testing.T) {
694 for src, itemList := range data.References {
695 t.Run(SpanName(src), func(t *testing.T) {
697 tests.References(t, src, itemList)
702 t.Run("Renames", func(t *testing.T) {
704 for spn, newText := range data.Renames {
705 t.Run(uriName(spn.URI())+"_"+newText, func(t *testing.T) {
707 tests.Rename(t, spn, newText)
712 t.Run("PrepareRenames", func(t *testing.T) {
714 for src, want := range data.PrepareRenames {
715 t.Run(SpanName(src), func(t *testing.T) {
717 tests.PrepareRename(t, src, want)
722 t.Run("Symbols", func(t *testing.T) {
724 for uri, expectedSymbols := range data.Symbols {
725 t.Run(uriName(uri), func(t *testing.T) {
727 tests.Symbols(t, uri, expectedSymbols)
732 t.Run("WorkspaceSymbols", func(t *testing.T) {
735 for _, typ := range []WorkspaceSymbolsTestType{
736 WorkspaceSymbolsDefault,
737 WorkspaceSymbolsCaseSensitive,
738 WorkspaceSymbolsFuzzy,
740 for uri, cases := range data.WorkspaceSymbols[typ] {
741 for _, query := range cases {
746 t.Run(name, func(t *testing.T) {
748 tests.WorkspaceSymbols(t, uri, query, typ)
756 t.Run("SignatureHelp", func(t *testing.T) {
758 for spn, expectedSignature := range data.Signatures {
759 t.Run(SpanName(spn), func(t *testing.T) {
761 tests.SignatureHelp(t, spn, expectedSignature)
766 t.Run("Link", func(t *testing.T) {
768 for uri, wantLinks := range data.Links {
769 // If we are testing GOPATH, then we do not want links with the versions
770 // attached (pkg.go.dev/repoa/moda@v1.1.0/pkg), unless the file is a
771 // go.mod, then we can skip it altogether.
772 if data.Exported.Exporter == packagestest.GOPATH {
773 if strings.HasSuffix(uri.Filename(), ".mod") {
776 re := regexp.MustCompile(`@v\d+\.\d+\.[\w-]+`)
777 for i, link := range wantLinks {
778 wantLinks[i].Target = re.ReplaceAllString(link.Target, "")
781 t.Run(uriName(uri), func(t *testing.T) {
783 tests.Link(t, uri, wantLinks)
789 for _, golden := range data.golden {
790 if !golden.Modified {
793 sort.Slice(golden.Archive.Files, func(i, j int) bool {
794 return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name
796 if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil {
803 func checkData(t *testing.T, data *Data) {
804 buf := &bytes.Buffer{}
805 diagnosticsCount := 0
806 for _, want := range data.Diagnostics {
807 diagnosticsCount += len(want)
810 for _, want := range data.Links {
811 linksCount += len(want)
814 typeDefinitionCount := 0
815 for _, d := range data.Definitions {
817 typeDefinitionCount++
824 for _, want := range data.CompletionSnippets {
825 snippetCount += len(want)
828 countCompletions := func(c map[span.Span][]Completion) (count int) {
829 for _, want := range c {
835 countCodeLens := func(c map[span.URI][]protocol.CodeLens) (count int) {
836 for _, want := range c {
842 countWorkspaceSymbols := func(c map[WorkspaceSymbolsTestType]map[span.URI][]string) (count int) {
843 for _, typs := range c {
844 for _, queries := range typs {
845 count += len(queries)
851 fmt.Fprintf(buf, "CallHierarchyCount = %v\n", len(data.CallHierarchy))
852 fmt.Fprintf(buf, "CodeLensCount = %v\n", countCodeLens(data.CodeLens))
853 fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions))
854 fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount)
855 fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions))
856 fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", countCompletions(data.DeepCompletions))
857 fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", countCompletions(data.FuzzyCompletions))
858 fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", countCompletions(data.RankCompletions))
859 fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", countCompletions(data.CaseSensitiveCompletions))
860 fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount)
861 fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges))
862 fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats))
863 fmt.Fprintf(buf, "ImportCount = %v\n", len(data.Imports))
864 fmt.Fprintf(buf, "SemanticTokenCount = %v\n", len(data.SemanticTokens))
865 fmt.Fprintf(buf, "SuggestedFixCount = %v\n", len(data.SuggestedFixes))
866 fmt.Fprintf(buf, "FunctionExtractionCount = %v\n", len(data.FunctionExtractions))
867 fmt.Fprintf(buf, "DefinitionsCount = %v\n", definitionCount)
868 fmt.Fprintf(buf, "TypeDefinitionsCount = %v\n", typeDefinitionCount)
869 fmt.Fprintf(buf, "HighlightsCount = %v\n", len(data.Highlights))
870 fmt.Fprintf(buf, "ReferencesCount = %v\n", len(data.References))
871 fmt.Fprintf(buf, "RenamesCount = %v\n", len(data.Renames))
872 fmt.Fprintf(buf, "PrepareRenamesCount = %v\n", len(data.PrepareRenames))
873 fmt.Fprintf(buf, "SymbolsCount = %v\n", len(data.Symbols))
874 fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", countWorkspaceSymbols(data.WorkspaceSymbols))
875 fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures))
876 fmt.Fprintf(buf, "LinksCount = %v\n", linksCount)
877 fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations))
879 want := string(data.Golden("summary", summaryFile, func() ([]byte, error) {
880 return buf.Bytes(), nil
884 t.Errorf("test summary does not match:\n%s", Diff(t, want, got))
888 func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) {
889 data.mappersMu.Lock()
890 defer data.mappersMu.Unlock()
892 if _, ok := data.mappers[uri]; !ok {
893 content, err := data.Exported.FileContents(uri.Filename())
897 converter := span.NewContentConverter(uri.Filename(), content)
898 data.mappers[uri] = &protocol.ColumnMapper{
900 Converter: converter,
904 return data.mappers[uri], nil
907 func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte {
909 fragment, found := data.fragments[target]
911 if filepath.IsAbs(target) {
912 data.t.Fatalf("invalid golden file fragment %v", target)
916 golden := data.golden[fragment]
919 data.t.Fatalf("could not find golden file %v: %v", fragment, tag)
922 Filename: filepath.Join(data.dir, fragment+goldenFileSuffix),
923 Archive: &txtar.Archive{},
926 data.golden[fragment] = golden
929 for i := range golden.Archive.Files {
930 f := &golden.Archive.Files[i]
938 golden.Archive.Files = append(golden.Archive.Files, txtar.File{
941 file = &golden.Archive.Files[len(golden.Archive.Files)-1]
943 contents, err := update()
945 data.t.Fatalf("could not update golden file %v: %v", fragment, err)
947 file.Data = append(contents, '\n') // add trailing \n for txtar
948 golden.Modified = true
952 data.t.Fatalf("could not find golden contents %v: %v", fragment, tag)
954 if len(file.Data) == 0 {
957 return file.Data[:len(file.Data)-1] // drop the trailing \n
960 func (data *Data) collectCodeLens(spn span.Span, title, cmd string) {
961 if _, ok := data.CodeLens[spn.URI()]; !ok {
962 data.CodeLens[spn.URI()] = []protocol.CodeLens{}
964 m, err := data.Mapper(spn.URI())
968 rng, err := m.Range(spn)
972 data.CodeLens[spn.URI()] = append(data.CodeLens[spn.URI()], protocol.CodeLens{
974 Command: protocol.Command{
981 func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg, msgSeverity string) {
982 if _, ok := data.Diagnostics[spn.URI()]; !ok {
983 data.Diagnostics[spn.URI()] = []*source.Diagnostic{}
985 m, err := data.Mapper(spn.URI())
989 rng, err := m.Range(spn)
993 severity := protocol.SeverityError
996 severity = protocol.SeverityError
998 severity = protocol.SeverityWarning
1000 severity = protocol.SeverityHint
1002 severity = protocol.SeverityInformation
1004 // This is not the correct way to do this, but it seems excessive to do the full conversion here.
1005 want := &source.Diagnostic{
1008 Source: source.DiagnosticSource(msgSource),
1011 data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
1014 func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
1015 result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) {
1016 m[src] = append(m[src], Completion{
1017 CompletionItems: expected,
1021 case CompletionDeep:
1022 return func(src span.Span, expected []token.Pos) {
1023 result(data.DeepCompletions, src, expected)
1025 case CompletionUnimported:
1026 return func(src span.Span, expected []token.Pos) {
1027 result(data.UnimportedCompletions, src, expected)
1029 case CompletionFuzzy:
1030 return func(src span.Span, expected []token.Pos) {
1031 result(data.FuzzyCompletions, src, expected)
1033 case CompletionRank:
1034 return func(src span.Span, expected []token.Pos) {
1035 result(data.RankCompletions, src, expected)
1037 case CompletionCaseSensitive:
1038 return func(src span.Span, expected []token.Pos) {
1039 result(data.CaseSensitiveCompletions, src, expected)
1042 return func(src span.Span, expected []token.Pos) {
1043 result(data.Completions, src, expected)
1048 func (data *Data) collectCompletionItems(pos token.Pos, args []string) {
1050 loc := data.Exported.ExpectFileSet.Position(pos)
1051 data.t.Fatalf("%s:%d: @item expects at least 3 args, got %d",
1052 loc.Filename, loc.Line, len(args))
1054 label, detail, kind := args[0], args[1], args[2]
1055 var documentation string
1057 documentation = args[3]
1059 data.CompletionItems[pos] = &completion.CompletionItem{
1062 Kind: protocol.ParseCompletionItemKind(kind),
1063 Documentation: documentation,
1067 func (data *Data) collectFoldingRanges(spn span.Span) {
1068 data.FoldingRanges = append(data.FoldingRanges, spn)
1071 func (data *Data) collectFormats(spn span.Span) {
1072 data.Formats = append(data.Formats, spn)
1075 func (data *Data) collectImports(spn span.Span) {
1076 data.Imports = append(data.Imports, spn)
1079 func (data *Data) collectSemanticTokens(spn span.Span) {
1080 data.SemanticTokens = append(data.SemanticTokens, spn)
1083 func (data *Data) collectSuggestedFixes(spn span.Span, actionKind string) {
1084 if _, ok := data.SuggestedFixes[spn]; !ok {
1085 data.SuggestedFixes[spn] = []string{}
1087 data.SuggestedFixes[spn] = append(data.SuggestedFixes[spn], actionKind)
1090 func (data *Data) collectFunctionExtractions(start span.Span, end span.Span) {
1091 if _, ok := data.FunctionExtractions[start]; !ok {
1092 data.FunctionExtractions[start] = end
1096 func (data *Data) collectDefinitions(src, target span.Span) {
1097 data.Definitions[src] = Definition{
1103 func (data *Data) collectImplementations(src span.Span, targets []span.Span) {
1104 data.Implementations[src] = targets
1107 func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) {
1108 for _, call := range calls {
1109 m, err := data.Mapper(call.URI())
1113 rng, err := m.Range(call)
1117 // we're only comparing protocol.range
1118 if data.CallHierarchy[src] != nil {
1119 data.CallHierarchy[src].IncomingCalls = append(data.CallHierarchy[src].IncomingCalls,
1120 protocol.CallHierarchyItem{
1121 URI: protocol.DocumentURI(call.URI()),
1125 data.CallHierarchy[src] = &CallHierarchyResult{
1126 IncomingCalls: []protocol.CallHierarchyItem{
1127 {URI: protocol.DocumentURI(call.URI()), Range: rng},
1134 func (data *Data) collectOutgoingCalls(src span.Span, calls []span.Span) {
1135 if data.CallHierarchy[src] == nil {
1136 data.CallHierarchy[src] = &CallHierarchyResult{}
1138 for _, call := range calls {
1139 m, err := data.Mapper(call.URI())
1143 rng, err := m.Range(call)
1147 // we're only comparing protocol.range
1148 data.CallHierarchy[src].OutgoingCalls = append(data.CallHierarchy[src].OutgoingCalls,
1149 protocol.CallHierarchyItem{
1150 URI: protocol.DocumentURI(call.URI()),
1156 func (data *Data) collectHoverDefinitions(src, target span.Span) {
1157 data.Definitions[src] = Definition{
1164 func (data *Data) collectTypeDefinitions(src, target span.Span) {
1165 data.Definitions[src] = Definition{
1172 func (data *Data) collectDefinitionNames(src span.Span, name string) {
1173 d := data.Definitions[src]
1175 data.Definitions[src] = d
1178 func (data *Data) collectHighlights(src span.Span, expected []span.Span) {
1179 // Declaring a highlight in a test file: @highlight(src, expected1, expected2)
1180 data.Highlights[src] = append(data.Highlights[src], expected...)
1183 func (data *Data) collectReferences(src span.Span, expected []span.Span) {
1184 data.References[src] = expected
1187 func (data *Data) collectRenames(src span.Span, newText string) {
1188 data.Renames[src] = newText
1191 func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placeholder string) {
1192 m, err := data.Mapper(src.URI())
1196 // Convert range to span and then to protocol.Range.
1197 spn, err := rng.Span()
1201 prng, err := m.Range(spn)
1205 data.PrepareRenames[src] = &source.PrepareItem{
1211 // collectSymbols is responsible for collecting @symbol annotations.
1212 func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string, siName string) {
1213 m, err := data.Mapper(spn.URI())
1217 rng, err := m.Range(spn)
1221 sym := protocol.DocumentSymbol{
1223 Kind: protocol.ParseSymbolKind(kind),
1224 SelectionRange: rng,
1226 if parentName == "" {
1227 data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym)
1229 data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym)
1232 // Reuse @symbol in the workspace symbols tests.
1233 si := protocol.SymbolInformation{
1236 Location: protocol.Location{
1237 URI: protocol.URIFromSpanURI(spn.URI()),
1238 Range: sym.SelectionRange,
1241 data.symbolInformation[spn] = si
1244 func (data *Data) collectWorkspaceSymbols(typ WorkspaceSymbolsTestType) func(*expect.Note, string) {
1245 return func(note *expect.Note, query string) {
1246 if data.WorkspaceSymbols[typ] == nil {
1247 data.WorkspaceSymbols[typ] = make(map[span.URI][]string)
1249 pos := data.Exported.ExpectFileSet.Position(note.Pos)
1250 uri := span.URIFromPath(pos.Filename)
1251 data.WorkspaceSymbols[typ][uri] = append(data.WorkspaceSymbols[typ][uri], query)
1255 func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) {
1256 data.Signatures[spn] = &protocol.SignatureHelp{
1257 Signatures: []protocol.SignatureInformation{
1262 ActiveParameter: uint32(activeParam),
1264 // Hardcode special case to test the lack of a signature.
1265 if signature == "" && activeParam == 0 {
1266 data.Signatures[spn] = nil
1270 func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) {
1271 data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{
1272 CompletionItem: item,
1273 PlainSnippet: plain,
1274 PlaceholderSnippet: placeholder,
1278 func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {
1279 position := fset.Position(note.Pos)
1281 data.Links[uri] = append(data.Links[uri], Link{
1284 NotePosition: position,
1288 func uriName(uri span.URI) string {
1289 return filepath.Base(strings.TrimSuffix(uri.Filename(), ".go"))
1292 func SpanName(spn span.Span) string {
1293 return fmt.Sprintf("%v_%v_%v", uriName(spn.URI()), spn.Start().Line(), spn.Start().Column())
1296 func CopyFolderToTempDir(folder string) (string, error) {
1297 if _, err := os.Stat(folder); err != nil {
1300 dst, err := ioutil.TempDir("", "modfile_test")
1304 fds, err := ioutil.ReadDir(folder)
1308 for _, fd := range fds {
1309 srcfp := filepath.Join(folder, fd.Name())
1310 stat, err := os.Stat(srcfp)
1314 if !stat.Mode().IsRegular() {
1315 return "", fmt.Errorf("cannot copy non regular file %s", srcfp)
1317 contents, err := ioutil.ReadFile(srcfp)
1321 if err := ioutil.WriteFile(filepath.Join(dst, fd.Name()), contents, stat.Mode()); err != nil {
1328 func shouldSkip(data *Data, uri span.URI) bool {
1329 if data.ModfileFlagAvailable {
1332 // If the -modfile flag is not available, then we do not want to run
1333 // any tests on the go.mod file.
1334 if strings.HasSuffix(uri.Filename(), ".mod") {
1337 // If the -modfile flag is not available, then we do not want to test any
1338 // uri that contains "go mod tidy".
1339 m, err := data.Mapper(uri)
1340 return err == nil && strings.Contains(string(m.Content), ", \"go mod tidy\",")