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/protocol"
30 "golang.org/x/tools/internal/lsp/source"
31 "golang.org/x/tools/internal/lsp/source/completion"
32 "golang.org/x/tools/internal/span"
33 "golang.org/x/tools/internal/testenv"
34 "golang.org/x/tools/txtar"
38 overlayFileSuffix = ".overlay"
39 goldenFileSuffix = ".golden"
41 summaryFile = "summary.txt"
42 testModule = "golang.org/x/tools/internal/lsp"
45 var UpdateGolden = flag.Bool("golden", false, "Update golden files")
47 type CallHierarchy map[span.Span]*CallHierarchyResult
48 type CodeLens map[span.URI][]protocol.CodeLens
49 type Diagnostics map[span.URI][]*source.Diagnostic
50 type CompletionItems map[token.Pos]*completion.CompletionItem
51 type Completions map[span.Span][]Completion
52 type CompletionSnippets map[span.Span][]CompletionSnippet
53 type UnimportedCompletions map[span.Span][]Completion
54 type DeepCompletions map[span.Span][]Completion
55 type FuzzyCompletions map[span.Span][]Completion
56 type CaseSensitiveCompletions map[span.Span][]Completion
57 type RankCompletions map[span.Span][]Completion
58 type FoldingRanges []span.Span
59 type Formats []span.Span
60 type Imports []span.Span
61 type SemanticTokens []span.Span
62 type SuggestedFixes map[span.Span][]string
63 type FunctionExtractions map[span.Span]span.Span
64 type Definitions map[span.Span]Definition
65 type Implementations map[span.Span][]span.Span
66 type Highlights map[span.Span][]span.Span
67 type References map[span.Span][]span.Span
68 type Renames map[span.Span]string
69 type PrepareRenames map[span.Span]*source.PrepareItem
70 type Symbols map[span.URI][]protocol.DocumentSymbol
71 type SymbolsChildren map[string][]protocol.DocumentSymbol
72 type SymbolInformation map[span.Span]protocol.SymbolInformation
73 type WorkspaceSymbols map[WorkspaceSymbolsTestType]map[span.URI][]string
74 type Signatures map[span.Span]*protocol.SignatureHelp
75 type Links map[span.URI][]Link
78 Config packages.Config
79 Exported *packagestest.Exported
80 CallHierarchy CallHierarchy
82 Diagnostics Diagnostics
83 CompletionItems CompletionItems
84 Completions Completions
85 CompletionSnippets CompletionSnippets
86 UnimportedCompletions UnimportedCompletions
87 DeepCompletions DeepCompletions
88 FuzzyCompletions FuzzyCompletions
89 CaseSensitiveCompletions CaseSensitiveCompletions
90 RankCompletions RankCompletions
91 FoldingRanges FoldingRanges
94 SemanticTokens SemanticTokens
95 SuggestedFixes SuggestedFixes
96 FunctionExtractions FunctionExtractions
97 Definitions Definitions
98 Implementations Implementations
100 References References
102 PrepareRenames PrepareRenames
104 symbolsChildren SymbolsChildren
105 symbolInformation SymbolInformation
106 WorkspaceSymbols WorkspaceSymbols
107 Signatures Signatures
111 fragments map[string]string
113 golden map[string]*Golden
116 ModfileFlagAvailable bool
119 mappers map[span.URI]*protocol.ColumnMapper
122 type Tests interface {
123 CallHierarchy(*testing.T, span.Span, *CallHierarchyResult)
124 CodeLens(*testing.T, span.URI, []protocol.CodeLens)
125 Diagnostics(*testing.T, span.URI, []*source.Diagnostic)
126 Completion(*testing.T, span.Span, Completion, CompletionItems)
127 CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems)
128 UnimportedCompletion(*testing.T, span.Span, Completion, CompletionItems)
129 DeepCompletion(*testing.T, span.Span, Completion, CompletionItems)
130 FuzzyCompletion(*testing.T, span.Span, Completion, CompletionItems)
131 CaseSensitiveCompletion(*testing.T, span.Span, Completion, CompletionItems)
132 RankCompletion(*testing.T, span.Span, Completion, CompletionItems)
133 FoldingRanges(*testing.T, span.Span)
134 Format(*testing.T, span.Span)
135 Import(*testing.T, span.Span)
136 SemanticTokens(*testing.T, span.Span)
137 SuggestedFix(*testing.T, span.Span, []string, int)
138 FunctionExtraction(*testing.T, span.Span, span.Span)
139 Definition(*testing.T, span.Span, Definition)
140 Implementation(*testing.T, span.Span, []span.Span)
141 Highlight(*testing.T, span.Span, []span.Span)
142 References(*testing.T, span.Span, []span.Span)
143 Rename(*testing.T, span.Span, string)
144 PrepareRename(*testing.T, span.Span, *source.PrepareItem)
145 Symbols(*testing.T, span.URI, []protocol.DocumentSymbol)
146 WorkspaceSymbols(*testing.T, span.URI, string, WorkspaceSymbolsTestType)
147 SignatureHelp(*testing.T, span.Span, *protocol.SignatureHelp)
148 Link(*testing.T, span.URI, []Link)
151 type Definition struct {
158 type CompletionTestType int
161 // Default runs the standard completion tests.
162 CompletionDefault = CompletionTestType(iota)
164 // Unimported tests the autocompletion of unimported packages.
167 // Deep tests deep completion.
170 // Fuzzy tests deep completion and fuzzy matching.
173 // CaseSensitive tests case sensitive completion.
174 CompletionCaseSensitive
176 // CompletionRank candidates in test must be valid and in the right relative order.
180 type WorkspaceSymbolsTestType int
183 // Default runs the standard workspace symbols tests.
184 WorkspaceSymbolsDefault = WorkspaceSymbolsTestType(iota)
186 // Fuzzy tests workspace symbols with fuzzy matching.
187 WorkspaceSymbolsFuzzy
189 // CaseSensitive tests workspace symbols with case sensitive.
190 WorkspaceSymbolsCaseSensitive
193 type Completion struct {
194 CompletionItems []token.Pos
197 type CompletionSnippet struct {
198 CompletionItem token.Pos
200 PlaceholderSnippet string
203 type CallHierarchyResult struct {
204 IncomingCalls, OutgoingCalls []protocol.CallHierarchyItem
210 NotePosition token.Position
215 Archive *txtar.Archive
219 func Context(t testing.TB) context.Context {
220 return context.Background()
223 func DefaultOptions(o *source.Options) {
224 o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
226 protocol.SourceOrganizeImports: true,
227 protocol.QuickFix: true,
228 protocol.RefactorRewrite: true,
229 protocol.RefactorExtract: true,
230 protocol.SourceFixAll: true,
233 protocol.SourceOrganizeImports: true,
237 o.UserOptions.Codelenses[source.CommandTest.Name] = true
238 o.HoverKind = source.SynopsisDocumentation
239 o.InsertTextFormat = protocol.SnippetTextFormat
240 o.CompletionBudget = time.Minute
241 o.HierarchicalDocumentSymbolSupport = true
242 o.ExperimentalWorkspaceModule = true
243 o.SemanticTokens = true
246 func RunTests(t *testing.T, dataDir string, includeMultiModule bool, f func(*testing.T, *Data)) {
248 modes := []string{"Modules", "GOPATH"}
249 if includeMultiModule {
250 modes = append(modes, "MultiModule")
252 for _, mode := range modes {
253 t.Run(mode, func(t *testing.T) {
255 if mode == "MultiModule" {
256 // Some bug in 1.12 breaks reading markers, and it's not worth figuring out.
257 testenv.NeedsGo1Point(t, 13)
259 datum := load(t, mode, dataDir)
265 func load(t testing.TB, mode string, dir string) *Data {
269 CallHierarchy: make(CallHierarchy),
270 CodeLens: make(CodeLens),
271 Diagnostics: make(Diagnostics),
272 CompletionItems: make(CompletionItems),
273 Completions: make(Completions),
274 CompletionSnippets: make(CompletionSnippets),
275 UnimportedCompletions: make(UnimportedCompletions),
276 DeepCompletions: make(DeepCompletions),
277 FuzzyCompletions: make(FuzzyCompletions),
278 RankCompletions: make(RankCompletions),
279 CaseSensitiveCompletions: make(CaseSensitiveCompletions),
280 Definitions: make(Definitions),
281 Implementations: make(Implementations),
282 Highlights: make(Highlights),
283 References: make(References),
284 Renames: make(Renames),
285 PrepareRenames: make(PrepareRenames),
286 SuggestedFixes: make(SuggestedFixes),
287 FunctionExtractions: make(FunctionExtractions),
288 Symbols: make(Symbols),
289 symbolsChildren: make(SymbolsChildren),
290 symbolInformation: make(SymbolInformation),
291 WorkspaceSymbols: make(WorkspaceSymbols),
292 Signatures: make(Signatures),
297 fragments: map[string]string{},
298 golden: map[string]*Golden{},
300 mappers: map[span.URI]*protocol.ColumnMapper{},
304 summary := filepath.Join(filepath.FromSlash(dir), summaryFile+goldenFileSuffix)
305 if _, err := os.Stat(summary); os.IsNotExist(err) {
306 t.Fatalf("could not find golden file summary.txt in %#v", dir)
308 archive, err := txtar.ParseFile(summary)
310 t.Fatalf("could not read golden file %v/%v: %v", dir, summary, err)
312 datum.golden[summaryFile] = &Golden{
318 files := packagestest.MustCopyFileTree(dir)
319 overlays := map[string][]byte{}
320 for fragment, operation := range files {
321 if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {
322 delete(files, fragment)
323 goldFile := filepath.Join(dir, fragment)
324 archive, err := txtar.ParseFile(goldFile)
326 t.Fatalf("could not read golden file %v: %v", fragment, err)
328 datum.golden[trimmed] = &Golden{
332 } else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment {
333 delete(files, fragment)
334 files[trimmed] = operation
335 } else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 {
336 delete(files, fragment)
337 partial := fragment[:index] + fragment[index+len(overlayFileSuffix):]
338 contents, err := ioutil.ReadFile(filepath.Join(dir, fragment))
342 overlays[partial] = contents
346 modules := []packagestest.Module{
355 datum.Exported = packagestest.Export(t, packagestest.Modules, modules)
357 datum.Exported = packagestest.Export(t, packagestest.GOPATH, modules)
359 files := map[string]interface{}{}
360 for k, v := range modules[0].Files {
361 files[filepath.Join("testmodule", k)] = v
363 modules[0].Files = files
365 overlays := map[string][]byte{}
366 for k, v := range modules[0].Overlay {
367 overlays[filepath.Join("testmodule", k)] = v
369 modules[0].Overlay = overlays
371 golden := map[string]*Golden{}
372 for k, v := range datum.golden {
373 if k == summaryFile {
376 golden[filepath.Join("testmodule", k)] = v
379 datum.golden = golden
381 datum.Exported = packagestest.Export(t, packagestest.Modules, modules)
383 panic("unknown mode " + mode)
386 for _, m := range modules {
387 for fragment := range m.Files {
388 filename := datum.Exported.File(m.Name, fragment)
389 datum.fragments[filename] = fragment
393 // Turn off go/packages debug logging.
394 datum.Exported.Config.Logf = nil
395 datum.Config.Logf = nil
397 // Merge the exported.Config with the view.Config.
398 datum.Config = *datum.Exported.Config
399 datum.Config.Fset = token.NewFileSet()
400 datum.Config.Context = Context(nil)
401 datum.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
402 panic("ParseFile should not be called")
405 // Do a first pass to collect special markers for completion and workspace symbols.
406 if err := datum.Exported.Expect(map[string]interface{}{
407 "item": func(name string, r packagestest.Range, _ []string) {
408 datum.Exported.Mark(name, r)
410 "symbol": func(name string, r packagestest.Range, _ []string) {
411 datum.Exported.Mark(name, r)
417 // Collect any data that needs to be used by subsequent tests.
418 if err := datum.Exported.Expect(map[string]interface{}{
419 "codelens": datum.collectCodeLens,
420 "diag": datum.collectDiagnostics,
421 "item": datum.collectCompletionItems,
422 "complete": datum.collectCompletions(CompletionDefault),
423 "unimported": datum.collectCompletions(CompletionUnimported),
424 "deep": datum.collectCompletions(CompletionDeep),
425 "fuzzy": datum.collectCompletions(CompletionFuzzy),
426 "casesensitive": datum.collectCompletions(CompletionCaseSensitive),
427 "rank": datum.collectCompletions(CompletionRank),
428 "snippet": datum.collectCompletionSnippets,
429 "fold": datum.collectFoldingRanges,
430 "format": datum.collectFormats,
431 "import": datum.collectImports,
432 "semantic": datum.collectSemanticTokens,
433 "godef": datum.collectDefinitions,
434 "implementations": datum.collectImplementations,
435 "typdef": datum.collectTypeDefinitions,
436 "hover": datum.collectHoverDefinitions,
437 "highlight": datum.collectHighlights,
438 "refs": datum.collectReferences,
439 "rename": datum.collectRenames,
440 "prepare": datum.collectPrepareRenames,
441 "symbol": datum.collectSymbols,
442 "signature": datum.collectSignatures,
443 "link": datum.collectLinks,
444 "suggestedfix": datum.collectSuggestedFixes,
445 "extractfunc": datum.collectFunctionExtractions,
446 "incomingcalls": datum.collectIncomingCalls,
447 "outgoingcalls": datum.collectOutgoingCalls,
451 for _, symbols := range datum.Symbols {
452 for i := range symbols {
453 children := datum.symbolsChildren[symbols[i].Name]
454 symbols[i].Children = children
457 // Collect names for the entries that require golden files.
458 if err := datum.Exported.Expect(map[string]interface{}{
459 "godef": datum.collectDefinitionNames,
460 "hover": datum.collectDefinitionNames,
461 "workspacesymbol": datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault),
462 "workspacesymbolfuzzy": datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy),
463 "workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive),
467 if mode == "MultiModule" {
468 if err := os.Rename(filepath.Join(datum.Config.Dir, "go.mod"), filepath.Join(datum.Config.Dir, "testmodule/go.mod")); err != nil {
476 func Run(t *testing.T, tests Tests, data *Data) {
480 eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) {
483 for src, exp := range cases {
484 for i, e := range exp {
485 t.Run(SpanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) {
487 if strings.Contains(t.Name(), "cgo") {
488 testenv.NeedsTool(t, "cgo")
490 if strings.Contains(t.Name(), "declarecgo") {
491 testenv.NeedsGo1Point(t, 15)
493 test(t, src, e, data.CompletionItems)
500 t.Run("CallHierarchy", func(t *testing.T) {
502 for spn, callHierarchyResult := range data.CallHierarchy {
503 t.Run(SpanName(spn), func(t *testing.T) {
505 tests.CallHierarchy(t, spn, callHierarchyResult)
510 t.Run("Completion", func(t *testing.T) {
512 eachCompletion(t, data.Completions, tests.Completion)
515 t.Run("CompletionSnippets", func(t *testing.T) {
517 for _, placeholders := range []bool{true, false} {
518 for src, expecteds := range data.CompletionSnippets {
519 for i, expected := range expecteds {
520 name := SpanName(src) + "_" + strconv.Itoa(i+1)
522 name += "_placeholders"
525 t.Run(name, func(t *testing.T) {
527 tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems)
534 t.Run("UnimportedCompletion", func(t *testing.T) {
536 eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion)
539 t.Run("DeepCompletion", func(t *testing.T) {
541 eachCompletion(t, data.DeepCompletions, tests.DeepCompletion)
544 t.Run("FuzzyCompletion", func(t *testing.T) {
546 eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion)
549 t.Run("CaseSensitiveCompletion", func(t *testing.T) {
551 eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion)
554 t.Run("RankCompletions", func(t *testing.T) {
556 eachCompletion(t, data.RankCompletions, tests.RankCompletion)
559 t.Run("CodeLens", func(t *testing.T) {
561 for uri, want := range data.CodeLens {
562 // Check if we should skip this URI if the -modfile flag is not available.
563 if shouldSkip(data, uri) {
566 t.Run(uriName(uri), func(t *testing.T) {
568 tests.CodeLens(t, uri, want)
573 t.Run("Diagnostics", func(t *testing.T) {
575 for uri, want := range data.Diagnostics {
576 // Check if we should skip this URI if the -modfile flag is not available.
577 if shouldSkip(data, uri) {
580 t.Run(uriName(uri), func(t *testing.T) {
582 tests.Diagnostics(t, uri, want)
587 t.Run("FoldingRange", func(t *testing.T) {
589 for _, spn := range data.FoldingRanges {
590 t.Run(uriName(spn.URI()), func(t *testing.T) {
592 tests.FoldingRanges(t, spn)
597 t.Run("Format", func(t *testing.T) {
599 for _, spn := range data.Formats {
600 t.Run(uriName(spn.URI()), func(t *testing.T) {
607 t.Run("Import", func(t *testing.T) {
609 for _, spn := range data.Imports {
610 t.Run(uriName(spn.URI()), func(t *testing.T) {
617 t.Run("SemanticTokens", func(t *testing.T) {
619 for _, spn := range data.SemanticTokens {
620 t.Run(uriName(spn.URI()), func(t *testing.T) {
622 tests.SemanticTokens(t, spn)
627 t.Run("SuggestedFix", func(t *testing.T) {
629 for spn, actionKinds := range data.SuggestedFixes {
630 // Check if we should skip this spn if the -modfile flag is not available.
631 if shouldSkip(data, spn.URI()) {
634 t.Run(SpanName(spn), func(t *testing.T) {
636 tests.SuggestedFix(t, spn, actionKinds, 1)
641 t.Run("FunctionExtraction", func(t *testing.T) {
643 for start, end := range data.FunctionExtractions {
644 // Check if we should skip this spn if the -modfile flag is not available.
645 if shouldSkip(data, start.URI()) {
648 t.Run(SpanName(start), func(t *testing.T) {
650 tests.FunctionExtraction(t, start, end)
655 t.Run("Definition", func(t *testing.T) {
657 for spn, d := range data.Definitions {
658 t.Run(SpanName(spn), func(t *testing.T) {
660 if strings.Contains(t.Name(), "cgo") {
661 testenv.NeedsTool(t, "cgo")
663 if strings.Contains(t.Name(), "declarecgo") {
664 testenv.NeedsGo1Point(t, 15)
666 tests.Definition(t, spn, d)
671 t.Run("Implementation", func(t *testing.T) {
673 for spn, m := range data.Implementations {
674 t.Run(SpanName(spn), func(t *testing.T) {
676 tests.Implementation(t, spn, m)
681 t.Run("Highlight", func(t *testing.T) {
683 for pos, locations := range data.Highlights {
684 t.Run(SpanName(pos), func(t *testing.T) {
686 tests.Highlight(t, pos, locations)
691 t.Run("References", func(t *testing.T) {
693 for src, itemList := range data.References {
694 t.Run(SpanName(src), func(t *testing.T) {
696 tests.References(t, src, itemList)
701 t.Run("Renames", func(t *testing.T) {
703 for spn, newText := range data.Renames {
704 t.Run(uriName(spn.URI())+"_"+newText, func(t *testing.T) {
706 tests.Rename(t, spn, newText)
711 t.Run("PrepareRenames", func(t *testing.T) {
713 for src, want := range data.PrepareRenames {
714 t.Run(SpanName(src), func(t *testing.T) {
716 tests.PrepareRename(t, src, want)
721 t.Run("Symbols", func(t *testing.T) {
723 for uri, expectedSymbols := range data.Symbols {
724 t.Run(uriName(uri), func(t *testing.T) {
726 tests.Symbols(t, uri, expectedSymbols)
731 t.Run("WorkspaceSymbols", func(t *testing.T) {
734 for _, typ := range []WorkspaceSymbolsTestType{
735 WorkspaceSymbolsDefault,
736 WorkspaceSymbolsCaseSensitive,
737 WorkspaceSymbolsFuzzy,
739 for uri, cases := range data.WorkspaceSymbols[typ] {
740 for _, query := range cases {
745 t.Run(name, func(t *testing.T) {
747 tests.WorkspaceSymbols(t, uri, query, typ)
755 t.Run("SignatureHelp", func(t *testing.T) {
757 for spn, expectedSignature := range data.Signatures {
758 t.Run(SpanName(spn), func(t *testing.T) {
760 tests.SignatureHelp(t, spn, expectedSignature)
765 t.Run("Link", func(t *testing.T) {
767 for uri, wantLinks := range data.Links {
768 // If we are testing GOPATH, then we do not want links with the versions
769 // attached (pkg.go.dev/repoa/moda@v1.1.0/pkg), unless the file is a
770 // go.mod, then we can skip it altogether.
771 if data.Exported.Exporter == packagestest.GOPATH {
772 if strings.HasSuffix(uri.Filename(), ".mod") {
775 re := regexp.MustCompile(`@v\d+\.\d+\.[\w-]+`)
776 for i, link := range wantLinks {
777 wantLinks[i].Target = re.ReplaceAllString(link.Target, "")
780 t.Run(uriName(uri), func(t *testing.T) {
782 tests.Link(t, uri, wantLinks)
788 for _, golden := range data.golden {
789 if !golden.Modified {
792 sort.Slice(golden.Archive.Files, func(i, j int) bool {
793 return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name
795 if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil {
802 func checkData(t *testing.T, data *Data) {
803 buf := &bytes.Buffer{}
804 diagnosticsCount := 0
805 for _, want := range data.Diagnostics {
806 diagnosticsCount += len(want)
809 for _, want := range data.Links {
810 linksCount += len(want)
813 typeDefinitionCount := 0
814 for _, d := range data.Definitions {
816 typeDefinitionCount++
823 for _, want := range data.CompletionSnippets {
824 snippetCount += len(want)
827 countCompletions := func(c map[span.Span][]Completion) (count int) {
828 for _, want := range c {
834 countCodeLens := func(c map[span.URI][]protocol.CodeLens) (count int) {
835 for _, want := range c {
841 countWorkspaceSymbols := func(c map[WorkspaceSymbolsTestType]map[span.URI][]string) (count int) {
842 for _, typs := range c {
843 for _, queries := range typs {
844 count += len(queries)
850 fmt.Fprintf(buf, "CallHierarchyCount = %v\n", len(data.CallHierarchy))
851 fmt.Fprintf(buf, "CodeLensCount = %v\n", countCodeLens(data.CodeLens))
852 fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions))
853 fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount)
854 fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions))
855 fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", countCompletions(data.DeepCompletions))
856 fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", countCompletions(data.FuzzyCompletions))
857 fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", countCompletions(data.RankCompletions))
858 fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", countCompletions(data.CaseSensitiveCompletions))
859 fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount)
860 fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges))
861 fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats))
862 fmt.Fprintf(buf, "ImportCount = %v\n", len(data.Imports))
863 fmt.Fprintf(buf, "SemanticTokenCount = %v\n", len(data.SemanticTokens))
864 fmt.Fprintf(buf, "SuggestedFixCount = %v\n", len(data.SuggestedFixes))
865 fmt.Fprintf(buf, "FunctionExtractionCount = %v\n", len(data.FunctionExtractions))
866 fmt.Fprintf(buf, "DefinitionsCount = %v\n", definitionCount)
867 fmt.Fprintf(buf, "TypeDefinitionsCount = %v\n", typeDefinitionCount)
868 fmt.Fprintf(buf, "HighlightsCount = %v\n", len(data.Highlights))
869 fmt.Fprintf(buf, "ReferencesCount = %v\n", len(data.References))
870 fmt.Fprintf(buf, "RenamesCount = %v\n", len(data.Renames))
871 fmt.Fprintf(buf, "PrepareRenamesCount = %v\n", len(data.PrepareRenames))
872 fmt.Fprintf(buf, "SymbolsCount = %v\n", len(data.Symbols))
873 fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", countWorkspaceSymbols(data.WorkspaceSymbols))
874 fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures))
875 fmt.Fprintf(buf, "LinksCount = %v\n", linksCount)
876 fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations))
878 want := string(data.Golden("summary", summaryFile, func() ([]byte, error) {
879 return buf.Bytes(), nil
883 t.Errorf("test summary does not match:\n%s", Diff(t, want, got))
887 func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) {
888 data.mappersMu.Lock()
889 defer data.mappersMu.Unlock()
891 if _, ok := data.mappers[uri]; !ok {
892 content, err := data.Exported.FileContents(uri.Filename())
896 converter := span.NewContentConverter(uri.Filename(), content)
897 data.mappers[uri] = &protocol.ColumnMapper{
899 Converter: converter,
903 return data.mappers[uri], nil
906 func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte {
908 fragment, found := data.fragments[target]
910 if filepath.IsAbs(target) {
911 data.t.Fatalf("invalid golden file fragment %v", target)
915 golden := data.golden[fragment]
918 data.t.Fatalf("could not find golden file %v: %v", fragment, tag)
921 Filename: filepath.Join(data.dir, fragment+goldenFileSuffix),
922 Archive: &txtar.Archive{},
925 data.golden[fragment] = golden
928 for i := range golden.Archive.Files {
929 f := &golden.Archive.Files[i]
937 golden.Archive.Files = append(golden.Archive.Files, txtar.File{
940 file = &golden.Archive.Files[len(golden.Archive.Files)-1]
942 contents, err := update()
944 data.t.Fatalf("could not update golden file %v: %v", fragment, err)
946 file.Data = append(contents, '\n') // add trailing \n for txtar
947 golden.Modified = true
951 data.t.Fatalf("could not find golden contents %v: %v", fragment, tag)
953 if len(file.Data) == 0 {
956 return file.Data[:len(file.Data)-1] // drop the trailing \n
959 func (data *Data) collectCodeLens(spn span.Span, title, cmd string) {
960 if _, ok := data.CodeLens[spn.URI()]; !ok {
961 data.CodeLens[spn.URI()] = []protocol.CodeLens{}
963 m, err := data.Mapper(spn.URI())
967 rng, err := m.Range(spn)
971 data.CodeLens[spn.URI()] = append(data.CodeLens[spn.URI()], protocol.CodeLens{
973 Command: protocol.Command{
980 func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg, msgSeverity string) {
981 if _, ok := data.Diagnostics[spn.URI()]; !ok {
982 data.Diagnostics[spn.URI()] = []*source.Diagnostic{}
984 m, err := data.Mapper(spn.URI())
988 rng, err := m.Range(spn)
992 severity := protocol.SeverityError
995 severity = protocol.SeverityError
997 severity = protocol.SeverityWarning
999 severity = protocol.SeverityHint
1001 severity = protocol.SeverityInformation
1003 // This is not the correct way to do this, but it seems excessive to do the full conversion here.
1004 want := &source.Diagnostic{
1010 data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
1013 func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
1014 result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) {
1015 m[src] = append(m[src], Completion{
1016 CompletionItems: expected,
1020 case CompletionDeep:
1021 return func(src span.Span, expected []token.Pos) {
1022 result(data.DeepCompletions, src, expected)
1024 case CompletionUnimported:
1025 return func(src span.Span, expected []token.Pos) {
1026 result(data.UnimportedCompletions, src, expected)
1028 case CompletionFuzzy:
1029 return func(src span.Span, expected []token.Pos) {
1030 result(data.FuzzyCompletions, src, expected)
1032 case CompletionRank:
1033 return func(src span.Span, expected []token.Pos) {
1034 result(data.RankCompletions, src, expected)
1036 case CompletionCaseSensitive:
1037 return func(src span.Span, expected []token.Pos) {
1038 result(data.CaseSensitiveCompletions, src, expected)
1041 return func(src span.Span, expected []token.Pos) {
1042 result(data.Completions, src, expected)
1047 func (data *Data) collectCompletionItems(pos token.Pos, args []string) {
1049 loc := data.Exported.ExpectFileSet.Position(pos)
1050 data.t.Fatalf("%s:%d: @item expects at least 3 args, got %d",
1051 loc.Filename, loc.Line, len(args))
1053 label, detail, kind := args[0], args[1], args[2]
1054 var documentation string
1056 documentation = args[3]
1058 data.CompletionItems[pos] = &completion.CompletionItem{
1061 Kind: protocol.ParseCompletionItemKind(kind),
1062 Documentation: documentation,
1066 func (data *Data) collectFoldingRanges(spn span.Span) {
1067 data.FoldingRanges = append(data.FoldingRanges, spn)
1070 func (data *Data) collectFormats(spn span.Span) {
1071 data.Formats = append(data.Formats, spn)
1074 func (data *Data) collectImports(spn span.Span) {
1075 data.Imports = append(data.Imports, spn)
1078 func (data *Data) collectSemanticTokens(spn span.Span) {
1079 data.SemanticTokens = append(data.SemanticTokens, spn)
1082 func (data *Data) collectSuggestedFixes(spn span.Span, actionKind string) {
1083 if _, ok := data.SuggestedFixes[spn]; !ok {
1084 data.SuggestedFixes[spn] = []string{}
1086 data.SuggestedFixes[spn] = append(data.SuggestedFixes[spn], actionKind)
1089 func (data *Data) collectFunctionExtractions(start span.Span, end span.Span) {
1090 if _, ok := data.FunctionExtractions[start]; !ok {
1091 data.FunctionExtractions[start] = end
1095 func (data *Data) collectDefinitions(src, target span.Span) {
1096 data.Definitions[src] = Definition{
1102 func (data *Data) collectImplementations(src span.Span, targets []span.Span) {
1103 data.Implementations[src] = targets
1106 func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) {
1107 for _, call := range calls {
1108 m, err := data.Mapper(call.URI())
1112 rng, err := m.Range(call)
1116 // we're only comparing protocol.range
1117 if data.CallHierarchy[src] != nil {
1118 data.CallHierarchy[src].IncomingCalls = append(data.CallHierarchy[src].IncomingCalls,
1119 protocol.CallHierarchyItem{
1120 URI: protocol.DocumentURI(call.URI()),
1124 data.CallHierarchy[src] = &CallHierarchyResult{
1125 IncomingCalls: []protocol.CallHierarchyItem{
1126 {URI: protocol.DocumentURI(call.URI()), Range: rng},
1133 func (data *Data) collectOutgoingCalls(src span.Span, calls []span.Span) {
1134 if data.CallHierarchy[src] == nil {
1135 data.CallHierarchy[src] = &CallHierarchyResult{}
1137 for _, call := range calls {
1138 m, err := data.Mapper(call.URI())
1142 rng, err := m.Range(call)
1146 // we're only comparing protocol.range
1147 data.CallHierarchy[src].OutgoingCalls = append(data.CallHierarchy[src].OutgoingCalls,
1148 protocol.CallHierarchyItem{
1149 URI: protocol.DocumentURI(call.URI()),
1155 func (data *Data) collectHoverDefinitions(src, target span.Span) {
1156 data.Definitions[src] = Definition{
1163 func (data *Data) collectTypeDefinitions(src, target span.Span) {
1164 data.Definitions[src] = Definition{
1171 func (data *Data) collectDefinitionNames(src span.Span, name string) {
1172 d := data.Definitions[src]
1174 data.Definitions[src] = d
1177 func (data *Data) collectHighlights(src span.Span, expected []span.Span) {
1178 // Declaring a highlight in a test file: @highlight(src, expected1, expected2)
1179 data.Highlights[src] = append(data.Highlights[src], expected...)
1182 func (data *Data) collectReferences(src span.Span, expected []span.Span) {
1183 data.References[src] = expected
1186 func (data *Data) collectRenames(src span.Span, newText string) {
1187 data.Renames[src] = newText
1190 func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placeholder string) {
1191 m, err := data.Mapper(src.URI())
1195 // Convert range to span and then to protocol.Range.
1196 spn, err := rng.Span()
1200 prng, err := m.Range(spn)
1204 data.PrepareRenames[src] = &source.PrepareItem{
1210 // collectSymbols is responsible for collecting @symbol annotations.
1211 func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string, siName string) {
1212 m, err := data.Mapper(spn.URI())
1216 rng, err := m.Range(spn)
1220 sym := protocol.DocumentSymbol{
1222 Kind: protocol.ParseSymbolKind(kind),
1223 SelectionRange: rng,
1225 if parentName == "" {
1226 data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym)
1228 data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym)
1231 // Reuse @symbol in the workspace symbols tests.
1232 si := protocol.SymbolInformation{
1235 Location: protocol.Location{
1236 URI: protocol.URIFromSpanURI(spn.URI()),
1237 Range: sym.SelectionRange,
1240 data.symbolInformation[spn] = si
1243 func (data *Data) collectWorkspaceSymbols(typ WorkspaceSymbolsTestType) func(*expect.Note, string) {
1244 return func(note *expect.Note, query string) {
1245 if data.WorkspaceSymbols[typ] == nil {
1246 data.WorkspaceSymbols[typ] = make(map[span.URI][]string)
1248 pos := data.Exported.ExpectFileSet.Position(note.Pos)
1249 uri := span.URIFromPath(pos.Filename)
1250 data.WorkspaceSymbols[typ][uri] = append(data.WorkspaceSymbols[typ][uri], query)
1254 func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) {
1255 data.Signatures[spn] = &protocol.SignatureHelp{
1256 Signatures: []protocol.SignatureInformation{
1261 ActiveParameter: float64(activeParam),
1263 // Hardcode special case to test the lack of a signature.
1264 if signature == "" && activeParam == 0 {
1265 data.Signatures[spn] = nil
1269 func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) {
1270 data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{
1271 CompletionItem: item,
1272 PlainSnippet: plain,
1273 PlaceholderSnippet: placeholder,
1277 func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {
1278 position := fset.Position(note.Pos)
1280 data.Links[uri] = append(data.Links[uri], Link{
1283 NotePosition: position,
1287 func uriName(uri span.URI) string {
1288 return filepath.Base(strings.TrimSuffix(uri.Filename(), ".go"))
1291 func SpanName(spn span.Span) string {
1292 return fmt.Sprintf("%v_%v_%v", uriName(spn.URI()), spn.Start().Line(), spn.Start().Column())
1295 func CopyFolderToTempDir(folder string) (string, error) {
1296 if _, err := os.Stat(folder); err != nil {
1299 dst, err := ioutil.TempDir("", "modfile_test")
1303 fds, err := ioutil.ReadDir(folder)
1307 for _, fd := range fds {
1308 srcfp := filepath.Join(folder, fd.Name())
1309 stat, err := os.Stat(srcfp)
1313 if !stat.Mode().IsRegular() {
1314 return "", fmt.Errorf("cannot copy non regular file %s", srcfp)
1316 contents, err := ioutil.ReadFile(srcfp)
1320 if err := ioutil.WriteFile(filepath.Join(dst, fd.Name()), contents, stat.Mode()); err != nil {
1327 func shouldSkip(data *Data, uri span.URI) bool {
1328 if data.ModfileFlagAvailable {
1331 // If the -modfile flag is not available, then we do not want to run
1332 // any tests on the go.mod file.
1333 if strings.HasSuffix(uri.Filename(), ".mod") {
1336 // If the -modfile flag is not available, then we do not want to test any
1337 // uri that contains "go mod tidy".
1338 m, err := data.Mapper(uri)
1339 return err == nil && strings.Contains(string(m.Content), ", \"go mod tidy\",")