github.com/v2fly/tools@v0.100.0/internal/lsp/tests/tests.go (about) 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. 4 5 // Package tests exports functionality to be used across a variety of gopls tests. 6 package tests 7 8 import ( 9 "bytes" 10 "context" 11 "flag" 12 "fmt" 13 "go/ast" 14 "go/token" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 "regexp" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/v2fly/tools/go/expect" 27 "github.com/v2fly/tools/go/packages" 28 "github.com/v2fly/tools/go/packages/packagestest" 29 "github.com/v2fly/tools/internal/lsp/command" 30 "github.com/v2fly/tools/internal/lsp/protocol" 31 "github.com/v2fly/tools/internal/lsp/source" 32 "github.com/v2fly/tools/internal/lsp/source/completion" 33 "github.com/v2fly/tools/internal/span" 34 "github.com/v2fly/tools/internal/testenv" 35 "github.com/v2fly/tools/txtar" 36 ) 37 38 const ( 39 overlayFileSuffix = ".overlay" 40 goldenFileSuffix = ".golden" 41 inFileSuffix = ".in" 42 summaryFile = "summary.txt" 43 testModule = "github.com/v2fly/tools/internal/lsp" 44 ) 45 46 var UpdateGolden = flag.Bool("golden", false, "Update golden files") 47 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 77 78 type Data struct { 79 Config packages.Config 80 Exported *packagestest.Exported 81 CallHierarchy CallHierarchy 82 CodeLens CodeLens 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 93 Formats Formats 94 Imports Imports 95 SemanticTokens SemanticTokens 96 SuggestedFixes SuggestedFixes 97 FunctionExtractions FunctionExtractions 98 Definitions Definitions 99 Implementations Implementations 100 Highlights Highlights 101 References References 102 Renames Renames 103 PrepareRenames PrepareRenames 104 Symbols Symbols 105 symbolsChildren SymbolsChildren 106 symbolInformation SymbolInformation 107 WorkspaceSymbols WorkspaceSymbols 108 Signatures Signatures 109 Links Links 110 111 t testing.TB 112 fragments map[string]string 113 dir string 114 golden map[string]*Golden 115 mode string 116 117 ModfileFlagAvailable bool 118 119 mappersMu sync.Mutex 120 mappers map[span.URI]*protocol.ColumnMapper 121 } 122 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) 150 } 151 152 type Definition struct { 153 Name string 154 IsType bool 155 OnlyHover bool 156 Src, Def span.Span 157 } 158 159 type CompletionTestType int 160 161 const ( 162 // Default runs the standard completion tests. 163 CompletionDefault = CompletionTestType(iota) 164 165 // Unimported tests the autocompletion of unimported packages. 166 CompletionUnimported 167 168 // Deep tests deep completion. 169 CompletionDeep 170 171 // Fuzzy tests deep completion and fuzzy matching. 172 CompletionFuzzy 173 174 // CaseSensitive tests case sensitive completion. 175 CompletionCaseSensitive 176 177 // CompletionRank candidates in test must be valid and in the right relative order. 178 CompletionRank 179 ) 180 181 type WorkspaceSymbolsTestType int 182 183 const ( 184 // Default runs the standard workspace symbols tests. 185 WorkspaceSymbolsDefault = WorkspaceSymbolsTestType(iota) 186 187 // Fuzzy tests workspace symbols with fuzzy matching. 188 WorkspaceSymbolsFuzzy 189 190 // CaseSensitive tests workspace symbols with case sensitive. 191 WorkspaceSymbolsCaseSensitive 192 ) 193 194 type Completion struct { 195 CompletionItems []token.Pos 196 } 197 198 type CompletionSnippet struct { 199 CompletionItem token.Pos 200 PlainSnippet string 201 PlaceholderSnippet string 202 } 203 204 type CallHierarchyResult struct { 205 IncomingCalls, OutgoingCalls []protocol.CallHierarchyItem 206 } 207 208 type Link struct { 209 Src span.Span 210 Target string 211 NotePosition token.Position 212 } 213 214 type Golden struct { 215 Filename string 216 Archive *txtar.Archive 217 Modified bool 218 } 219 220 func Context(t testing.TB) context.Context { 221 return context.Background() 222 } 223 224 func DefaultOptions(o *source.Options) { 225 o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{ 226 source.Go: { 227 protocol.SourceOrganizeImports: true, 228 protocol.QuickFix: true, 229 protocol.RefactorRewrite: true, 230 protocol.RefactorExtract: true, 231 protocol.SourceFixAll: true, 232 }, 233 source.Mod: { 234 protocol.SourceOrganizeImports: true, 235 }, 236 source.Sum: {}, 237 } 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 245 } 246 247 func RunTests(t *testing.T, dataDir string, includeMultiModule bool, f func(*testing.T, *Data)) { 248 t.Helper() 249 modes := []string{"Modules", "GOPATH"} 250 if includeMultiModule { 251 modes = append(modes, "MultiModule") 252 } 253 for _, mode := range modes { 254 t.Run(mode, func(t *testing.T) { 255 t.Helper() 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) 259 } 260 datum := load(t, mode, dataDir) 261 f(t, datum) 262 }) 263 } 264 } 265 266 func load(t testing.TB, mode string, dir string) *Data { 267 t.Helper() 268 269 datum := &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), 294 Links: make(Links), 295 296 t: t, 297 dir: dir, 298 fragments: map[string]string{}, 299 golden: map[string]*Golden{}, 300 mode: mode, 301 mappers: map[span.URI]*protocol.ColumnMapper{}, 302 } 303 304 if !*UpdateGolden { 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) 308 } 309 archive, err := txtar.ParseFile(summary) 310 if err != nil { 311 t.Fatalf("could not read golden file %v/%v: %v", dir, summary, err) 312 } 313 datum.golden[summaryFile] = &Golden{ 314 Filename: summary, 315 Archive: archive, 316 } 317 } 318 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) 326 if err != nil { 327 t.Fatalf("could not read golden file %v: %v", fragment, err) 328 } 329 datum.golden[trimmed] = &Golden{ 330 Filename: goldFile, 331 Archive: archive, 332 } 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)) 340 if err != nil { 341 t.Fatal(err) 342 } 343 overlays[partial] = contents 344 } 345 } 346 347 modules := []packagestest.Module{ 348 { 349 Name: testModule, 350 Files: files, 351 Overlay: overlays, 352 }, 353 } 354 switch mode { 355 case "Modules": 356 datum.Exported = packagestest.Export(t, packagestest.Modules, modules) 357 case "GOPATH": 358 datum.Exported = packagestest.Export(t, packagestest.GOPATH, modules) 359 case "MultiModule": 360 files := map[string]interface{}{} 361 for k, v := range modules[0].Files { 362 files[filepath.Join("testmodule", k)] = v 363 } 364 modules[0].Files = files 365 366 overlays := map[string][]byte{} 367 for k, v := range modules[0].Overlay { 368 overlays[filepath.Join("testmodule", k)] = v 369 } 370 modules[0].Overlay = overlays 371 372 golden := map[string]*Golden{} 373 for k, v := range datum.golden { 374 if k == summaryFile { 375 golden[k] = v 376 } else { 377 golden[filepath.Join("testmodule", k)] = v 378 } 379 } 380 datum.golden = golden 381 382 datum.Exported = packagestest.Export(t, packagestest.Modules, modules) 383 default: 384 panic("unknown mode " + mode) 385 } 386 387 for _, m := range modules { 388 for fragment := range m.Files { 389 filename := datum.Exported.File(m.Name, fragment) 390 datum.fragments[filename] = fragment 391 } 392 } 393 394 // Turn off go/packages debug logging. 395 datum.Exported.Config.Logf = nil 396 datum.Config.Logf = nil 397 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") 404 } 405 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) 410 }, 411 "symbol": func(name string, r packagestest.Range, _ []string) { 412 datum.Exported.Mark(name, r) 413 }, 414 }); err != nil { 415 t.Fatal(err) 416 } 417 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, 449 }); err != nil { 450 t.Fatal(err) 451 } 452 for _, symbols := range datum.Symbols { 453 for i := range symbols { 454 children := datum.symbolsChildren[symbols[i].Name] 455 symbols[i].Children = children 456 } 457 } 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), 465 }); err != nil { 466 t.Fatal(err) 467 } 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 { 470 t.Fatal(err) 471 } 472 } 473 474 return datum 475 } 476 477 func Run(t *testing.T, tests Tests, data *Data) { 478 t.Helper() 479 checkData(t, data) 480 481 eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) { 482 t.Helper() 483 484 for src, exp := range cases { 485 for i, e := range exp { 486 t.Run(SpanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) { 487 t.Helper() 488 if strings.Contains(t.Name(), "cgo") { 489 testenv.NeedsTool(t, "cgo") 490 } 491 if strings.Contains(t.Name(), "declarecgo") { 492 testenv.NeedsGo1Point(t, 15) 493 } 494 test(t, src, e, data.CompletionItems) 495 }) 496 } 497 498 } 499 } 500 501 t.Run("CallHierarchy", func(t *testing.T) { 502 t.Helper() 503 for spn, callHierarchyResult := range data.CallHierarchy { 504 t.Run(SpanName(spn), func(t *testing.T) { 505 t.Helper() 506 tests.CallHierarchy(t, spn, callHierarchyResult) 507 }) 508 } 509 }) 510 511 t.Run("Completion", func(t *testing.T) { 512 t.Helper() 513 eachCompletion(t, data.Completions, tests.Completion) 514 }) 515 516 t.Run("CompletionSnippets", func(t *testing.T) { 517 t.Helper() 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) 522 if placeholders { 523 name += "_placeholders" 524 } 525 526 t.Run(name, func(t *testing.T) { 527 t.Helper() 528 tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems) 529 }) 530 } 531 } 532 } 533 }) 534 535 t.Run("UnimportedCompletion", func(t *testing.T) { 536 t.Helper() 537 eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion) 538 }) 539 540 t.Run("DeepCompletion", func(t *testing.T) { 541 t.Helper() 542 eachCompletion(t, data.DeepCompletions, tests.DeepCompletion) 543 }) 544 545 t.Run("FuzzyCompletion", func(t *testing.T) { 546 t.Helper() 547 eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion) 548 }) 549 550 t.Run("CaseSensitiveCompletion", func(t *testing.T) { 551 t.Helper() 552 eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion) 553 }) 554 555 t.Run("RankCompletions", func(t *testing.T) { 556 t.Helper() 557 eachCompletion(t, data.RankCompletions, tests.RankCompletion) 558 }) 559 560 t.Run("CodeLens", func(t *testing.T) { 561 t.Helper() 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) { 565 continue 566 } 567 t.Run(uriName(uri), func(t *testing.T) { 568 t.Helper() 569 tests.CodeLens(t, uri, want) 570 }) 571 } 572 }) 573 574 t.Run("Diagnostics", func(t *testing.T) { 575 t.Helper() 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) { 579 continue 580 } 581 t.Run(uriName(uri), func(t *testing.T) { 582 t.Helper() 583 tests.Diagnostics(t, uri, want) 584 }) 585 } 586 }) 587 588 t.Run("FoldingRange", func(t *testing.T) { 589 t.Helper() 590 for _, spn := range data.FoldingRanges { 591 t.Run(uriName(spn.URI()), func(t *testing.T) { 592 t.Helper() 593 tests.FoldingRanges(t, spn) 594 }) 595 } 596 }) 597 598 t.Run("Format", func(t *testing.T) { 599 t.Helper() 600 for _, spn := range data.Formats { 601 t.Run(uriName(spn.URI()), func(t *testing.T) { 602 t.Helper() 603 tests.Format(t, spn) 604 }) 605 } 606 }) 607 608 t.Run("Import", func(t *testing.T) { 609 t.Helper() 610 for _, spn := range data.Imports { 611 t.Run(uriName(spn.URI()), func(t *testing.T) { 612 t.Helper() 613 tests.Import(t, spn) 614 }) 615 } 616 }) 617 618 t.Run("SemanticTokens", func(t *testing.T) { 619 t.Helper() 620 for _, spn := range data.SemanticTokens { 621 t.Run(uriName(spn.URI()), func(t *testing.T) { 622 t.Helper() 623 tests.SemanticTokens(t, spn) 624 }) 625 } 626 }) 627 628 t.Run("SuggestedFix", func(t *testing.T) { 629 t.Helper() 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()) { 633 continue 634 } 635 t.Run(SpanName(spn), func(t *testing.T) { 636 t.Helper() 637 tests.SuggestedFix(t, spn, actionKinds, 1) 638 }) 639 } 640 }) 641 642 t.Run("FunctionExtraction", func(t *testing.T) { 643 t.Helper() 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()) { 647 continue 648 } 649 t.Run(SpanName(start), func(t *testing.T) { 650 t.Helper() 651 tests.FunctionExtraction(t, start, end) 652 }) 653 } 654 }) 655 656 t.Run("Definition", func(t *testing.T) { 657 t.Helper() 658 for spn, d := range data.Definitions { 659 t.Run(SpanName(spn), func(t *testing.T) { 660 t.Helper() 661 if strings.Contains(t.Name(), "cgo") { 662 testenv.NeedsTool(t, "cgo") 663 } 664 if strings.Contains(t.Name(), "declarecgo") { 665 testenv.NeedsGo1Point(t, 15) 666 } 667 tests.Definition(t, spn, d) 668 }) 669 } 670 }) 671 672 t.Run("Implementation", func(t *testing.T) { 673 t.Helper() 674 for spn, m := range data.Implementations { 675 t.Run(SpanName(spn), func(t *testing.T) { 676 t.Helper() 677 tests.Implementation(t, spn, m) 678 }) 679 } 680 }) 681 682 t.Run("Highlight", func(t *testing.T) { 683 t.Helper() 684 for pos, locations := range data.Highlights { 685 t.Run(SpanName(pos), func(t *testing.T) { 686 t.Helper() 687 tests.Highlight(t, pos, locations) 688 }) 689 } 690 }) 691 692 t.Run("References", func(t *testing.T) { 693 t.Helper() 694 for src, itemList := range data.References { 695 t.Run(SpanName(src), func(t *testing.T) { 696 t.Helper() 697 tests.References(t, src, itemList) 698 }) 699 } 700 }) 701 702 t.Run("Renames", func(t *testing.T) { 703 t.Helper() 704 for spn, newText := range data.Renames { 705 t.Run(uriName(spn.URI())+"_"+newText, func(t *testing.T) { 706 t.Helper() 707 tests.Rename(t, spn, newText) 708 }) 709 } 710 }) 711 712 t.Run("PrepareRenames", func(t *testing.T) { 713 t.Helper() 714 for src, want := range data.PrepareRenames { 715 t.Run(SpanName(src), func(t *testing.T) { 716 t.Helper() 717 tests.PrepareRename(t, src, want) 718 }) 719 } 720 }) 721 722 t.Run("Symbols", func(t *testing.T) { 723 t.Helper() 724 for uri, expectedSymbols := range data.Symbols { 725 t.Run(uriName(uri), func(t *testing.T) { 726 t.Helper() 727 tests.Symbols(t, uri, expectedSymbols) 728 }) 729 } 730 }) 731 732 t.Run("WorkspaceSymbols", func(t *testing.T) { 733 t.Helper() 734 735 for _, typ := range []WorkspaceSymbolsTestType{ 736 WorkspaceSymbolsDefault, 737 WorkspaceSymbolsCaseSensitive, 738 WorkspaceSymbolsFuzzy, 739 } { 740 for uri, cases := range data.WorkspaceSymbols[typ] { 741 for _, query := range cases { 742 name := query 743 if name == "" { 744 name = "EmptyQuery" 745 } 746 t.Run(name, func(t *testing.T) { 747 t.Helper() 748 tests.WorkspaceSymbols(t, uri, query, typ) 749 }) 750 } 751 } 752 } 753 754 }) 755 756 t.Run("SignatureHelp", func(t *testing.T) { 757 t.Helper() 758 for spn, expectedSignature := range data.Signatures { 759 t.Run(SpanName(spn), func(t *testing.T) { 760 t.Helper() 761 tests.SignatureHelp(t, spn, expectedSignature) 762 }) 763 } 764 }) 765 766 t.Run("Link", func(t *testing.T) { 767 t.Helper() 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") { 774 continue 775 } 776 re := regexp.MustCompile(`@v\d+\.\d+\.[\w-]+`) 777 for i, link := range wantLinks { 778 wantLinks[i].Target = re.ReplaceAllString(link.Target, "") 779 } 780 } 781 t.Run(uriName(uri), func(t *testing.T) { 782 t.Helper() 783 tests.Link(t, uri, wantLinks) 784 }) 785 } 786 }) 787 788 if *UpdateGolden { 789 for _, golden := range data.golden { 790 if !golden.Modified { 791 continue 792 } 793 sort.Slice(golden.Archive.Files, func(i, j int) bool { 794 return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name 795 }) 796 if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil { 797 t.Fatal(err) 798 } 799 } 800 } 801 } 802 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) 808 } 809 linksCount := 0 810 for _, want := range data.Links { 811 linksCount += len(want) 812 } 813 definitionCount := 0 814 typeDefinitionCount := 0 815 for _, d := range data.Definitions { 816 if d.IsType { 817 typeDefinitionCount++ 818 } else { 819 definitionCount++ 820 } 821 } 822 823 snippetCount := 0 824 for _, want := range data.CompletionSnippets { 825 snippetCount += len(want) 826 } 827 828 countCompletions := func(c map[span.Span][]Completion) (count int) { 829 for _, want := range c { 830 count += len(want) 831 } 832 return count 833 } 834 835 countCodeLens := func(c map[span.URI][]protocol.CodeLens) (count int) { 836 for _, want := range c { 837 count += len(want) 838 } 839 return count 840 } 841 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) 846 } 847 } 848 return count 849 } 850 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)) 878 879 want := string(data.Golden("summary", summaryFile, func() ([]byte, error) { 880 return buf.Bytes(), nil 881 })) 882 got := buf.String() 883 if want != got { 884 t.Errorf("test summary does not match:\n%s", Diff(t, want, got)) 885 } 886 } 887 888 func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) { 889 data.mappersMu.Lock() 890 defer data.mappersMu.Unlock() 891 892 if _, ok := data.mappers[uri]; !ok { 893 content, err := data.Exported.FileContents(uri.Filename()) 894 if err != nil { 895 return nil, err 896 } 897 converter := span.NewContentConverter(uri.Filename(), content) 898 data.mappers[uri] = &protocol.ColumnMapper{ 899 URI: uri, 900 Converter: converter, 901 Content: content, 902 } 903 } 904 return data.mappers[uri], nil 905 } 906 907 func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte { 908 data.t.Helper() 909 fragment, found := data.fragments[target] 910 if !found { 911 if filepath.IsAbs(target) { 912 data.t.Fatalf("invalid golden file fragment %v", target) 913 } 914 fragment = target 915 } 916 golden := data.golden[fragment] 917 if golden == nil { 918 if !*UpdateGolden { 919 data.t.Fatalf("could not find golden file %v: %v", fragment, tag) 920 } 921 golden = &Golden{ 922 Filename: filepath.Join(data.dir, fragment+goldenFileSuffix), 923 Archive: &txtar.Archive{}, 924 Modified: true, 925 } 926 data.golden[fragment] = golden 927 } 928 var file *txtar.File 929 for i := range golden.Archive.Files { 930 f := &golden.Archive.Files[i] 931 if f.Name == tag { 932 file = f 933 break 934 } 935 } 936 if *UpdateGolden { 937 if file == nil { 938 golden.Archive.Files = append(golden.Archive.Files, txtar.File{ 939 Name: tag, 940 }) 941 file = &golden.Archive.Files[len(golden.Archive.Files)-1] 942 } 943 contents, err := update() 944 if err != nil { 945 data.t.Fatalf("could not update golden file %v: %v", fragment, err) 946 } 947 file.Data = append(contents, '\n') // add trailing \n for txtar 948 golden.Modified = true 949 950 } 951 if file == nil { 952 data.t.Fatalf("could not find golden contents %v: %v", fragment, tag) 953 } 954 if len(file.Data) == 0 { 955 return file.Data 956 } 957 return file.Data[:len(file.Data)-1] // drop the trailing \n 958 } 959 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{} 963 } 964 m, err := data.Mapper(spn.URI()) 965 if err != nil { 966 return 967 } 968 rng, err := m.Range(spn) 969 if err != nil { 970 return 971 } 972 data.CodeLens[spn.URI()] = append(data.CodeLens[spn.URI()], protocol.CodeLens{ 973 Range: rng, 974 Command: protocol.Command{ 975 Title: title, 976 Command: cmd, 977 }, 978 }) 979 } 980 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{} 984 } 985 m, err := data.Mapper(spn.URI()) 986 if err != nil { 987 return 988 } 989 rng, err := m.Range(spn) 990 if err != nil { 991 return 992 } 993 severity := protocol.SeverityError 994 switch msgSeverity { 995 case "error": 996 severity = protocol.SeverityError 997 case "warning": 998 severity = protocol.SeverityWarning 999 case "hint": 1000 severity = protocol.SeverityHint 1001 case "information": 1002 severity = protocol.SeverityInformation 1003 } 1004 // This is not the correct way to do this, but it seems excessive to do the full conversion here. 1005 want := &source.Diagnostic{ 1006 Range: rng, 1007 Severity: severity, 1008 Source: source.DiagnosticSource(msgSource), 1009 Message: msg, 1010 } 1011 data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want) 1012 } 1013 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, 1018 }) 1019 } 1020 switch typ { 1021 case CompletionDeep: 1022 return func(src span.Span, expected []token.Pos) { 1023 result(data.DeepCompletions, src, expected) 1024 } 1025 case CompletionUnimported: 1026 return func(src span.Span, expected []token.Pos) { 1027 result(data.UnimportedCompletions, src, expected) 1028 } 1029 case CompletionFuzzy: 1030 return func(src span.Span, expected []token.Pos) { 1031 result(data.FuzzyCompletions, src, expected) 1032 } 1033 case CompletionRank: 1034 return func(src span.Span, expected []token.Pos) { 1035 result(data.RankCompletions, src, expected) 1036 } 1037 case CompletionCaseSensitive: 1038 return func(src span.Span, expected []token.Pos) { 1039 result(data.CaseSensitiveCompletions, src, expected) 1040 } 1041 default: 1042 return func(src span.Span, expected []token.Pos) { 1043 result(data.Completions, src, expected) 1044 } 1045 } 1046 } 1047 1048 func (data *Data) collectCompletionItems(pos token.Pos, args []string) { 1049 if len(args) < 3 { 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)) 1053 } 1054 label, detail, kind := args[0], args[1], args[2] 1055 var documentation string 1056 if len(args) == 4 { 1057 documentation = args[3] 1058 } 1059 data.CompletionItems[pos] = &completion.CompletionItem{ 1060 Label: label, 1061 Detail: detail, 1062 Kind: protocol.ParseCompletionItemKind(kind), 1063 Documentation: documentation, 1064 } 1065 } 1066 1067 func (data *Data) collectFoldingRanges(spn span.Span) { 1068 data.FoldingRanges = append(data.FoldingRanges, spn) 1069 } 1070 1071 func (data *Data) collectFormats(spn span.Span) { 1072 data.Formats = append(data.Formats, spn) 1073 } 1074 1075 func (data *Data) collectImports(spn span.Span) { 1076 data.Imports = append(data.Imports, spn) 1077 } 1078 1079 func (data *Data) collectSemanticTokens(spn span.Span) { 1080 data.SemanticTokens = append(data.SemanticTokens, spn) 1081 } 1082 1083 func (data *Data) collectSuggestedFixes(spn span.Span, actionKind string) { 1084 if _, ok := data.SuggestedFixes[spn]; !ok { 1085 data.SuggestedFixes[spn] = []string{} 1086 } 1087 data.SuggestedFixes[spn] = append(data.SuggestedFixes[spn], actionKind) 1088 } 1089 1090 func (data *Data) collectFunctionExtractions(start span.Span, end span.Span) { 1091 if _, ok := data.FunctionExtractions[start]; !ok { 1092 data.FunctionExtractions[start] = end 1093 } 1094 } 1095 1096 func (data *Data) collectDefinitions(src, target span.Span) { 1097 data.Definitions[src] = Definition{ 1098 Src: src, 1099 Def: target, 1100 } 1101 } 1102 1103 func (data *Data) collectImplementations(src span.Span, targets []span.Span) { 1104 data.Implementations[src] = targets 1105 } 1106 1107 func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) { 1108 for _, call := range calls { 1109 m, err := data.Mapper(call.URI()) 1110 if err != nil { 1111 data.t.Fatal(err) 1112 } 1113 rng, err := m.Range(call) 1114 if err != nil { 1115 data.t.Fatal(err) 1116 } 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()), 1122 Range: rng, 1123 }) 1124 } else { 1125 data.CallHierarchy[src] = &CallHierarchyResult{ 1126 IncomingCalls: []protocol.CallHierarchyItem{ 1127 {URI: protocol.DocumentURI(call.URI()), Range: rng}, 1128 }, 1129 } 1130 } 1131 } 1132 } 1133 1134 func (data *Data) collectOutgoingCalls(src span.Span, calls []span.Span) { 1135 if data.CallHierarchy[src] == nil { 1136 data.CallHierarchy[src] = &CallHierarchyResult{} 1137 } 1138 for _, call := range calls { 1139 m, err := data.Mapper(call.URI()) 1140 if err != nil { 1141 data.t.Fatal(err) 1142 } 1143 rng, err := m.Range(call) 1144 if err != nil { 1145 data.t.Fatal(err) 1146 } 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()), 1151 Range: rng, 1152 }) 1153 } 1154 } 1155 1156 func (data *Data) collectHoverDefinitions(src, target span.Span) { 1157 data.Definitions[src] = Definition{ 1158 Src: src, 1159 Def: target, 1160 OnlyHover: true, 1161 } 1162 } 1163 1164 func (data *Data) collectTypeDefinitions(src, target span.Span) { 1165 data.Definitions[src] = Definition{ 1166 Src: src, 1167 Def: target, 1168 IsType: true, 1169 } 1170 } 1171 1172 func (data *Data) collectDefinitionNames(src span.Span, name string) { 1173 d := data.Definitions[src] 1174 d.Name = name 1175 data.Definitions[src] = d 1176 } 1177 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...) 1181 } 1182 1183 func (data *Data) collectReferences(src span.Span, expected []span.Span) { 1184 data.References[src] = expected 1185 } 1186 1187 func (data *Data) collectRenames(src span.Span, newText string) { 1188 data.Renames[src] = newText 1189 } 1190 1191 func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placeholder string) { 1192 m, err := data.Mapper(src.URI()) 1193 if err != nil { 1194 data.t.Fatal(err) 1195 } 1196 // Convert range to span and then to protocol.Range. 1197 spn, err := rng.Span() 1198 if err != nil { 1199 data.t.Fatal(err) 1200 } 1201 prng, err := m.Range(spn) 1202 if err != nil { 1203 data.t.Fatal(err) 1204 } 1205 data.PrepareRenames[src] = &source.PrepareItem{ 1206 Range: prng, 1207 Text: placeholder, 1208 } 1209 } 1210 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()) 1214 if err != nil { 1215 data.t.Fatal(err) 1216 } 1217 rng, err := m.Range(spn) 1218 if err != nil { 1219 data.t.Fatal(err) 1220 } 1221 sym := protocol.DocumentSymbol{ 1222 Name: name, 1223 Kind: protocol.ParseSymbolKind(kind), 1224 SelectionRange: rng, 1225 } 1226 if parentName == "" { 1227 data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym) 1228 } else { 1229 data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym) 1230 } 1231 1232 // Reuse @symbol in the workspace symbols tests. 1233 si := protocol.SymbolInformation{ 1234 Name: siName, 1235 Kind: sym.Kind, 1236 Location: protocol.Location{ 1237 URI: protocol.URIFromSpanURI(spn.URI()), 1238 Range: sym.SelectionRange, 1239 }, 1240 } 1241 data.symbolInformation[spn] = si 1242 } 1243 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) 1248 } 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) 1252 } 1253 } 1254 1255 func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) { 1256 data.Signatures[spn] = &protocol.SignatureHelp{ 1257 Signatures: []protocol.SignatureInformation{ 1258 { 1259 Label: signature, 1260 }, 1261 }, 1262 ActiveParameter: uint32(activeParam), 1263 } 1264 // Hardcode special case to test the lack of a signature. 1265 if signature == "" && activeParam == 0 { 1266 data.Signatures[spn] = nil 1267 } 1268 } 1269 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, 1275 }) 1276 } 1277 1278 func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) { 1279 position := fset.Position(note.Pos) 1280 uri := spn.URI() 1281 data.Links[uri] = append(data.Links[uri], Link{ 1282 Src: spn, 1283 Target: link, 1284 NotePosition: position, 1285 }) 1286 } 1287 1288 func uriName(uri span.URI) string { 1289 return filepath.Base(strings.TrimSuffix(uri.Filename(), ".go")) 1290 } 1291 1292 func SpanName(spn span.Span) string { 1293 return fmt.Sprintf("%v_%v_%v", uriName(spn.URI()), spn.Start().Line(), spn.Start().Column()) 1294 } 1295 1296 func CopyFolderToTempDir(folder string) (string, error) { 1297 if _, err := os.Stat(folder); err != nil { 1298 return "", err 1299 } 1300 dst, err := ioutil.TempDir("", "modfile_test") 1301 if err != nil { 1302 return "", err 1303 } 1304 fds, err := ioutil.ReadDir(folder) 1305 if err != nil { 1306 return "", err 1307 } 1308 for _, fd := range fds { 1309 srcfp := filepath.Join(folder, fd.Name()) 1310 stat, err := os.Stat(srcfp) 1311 if err != nil { 1312 return "", err 1313 } 1314 if !stat.Mode().IsRegular() { 1315 return "", fmt.Errorf("cannot copy non regular file %s", srcfp) 1316 } 1317 contents, err := ioutil.ReadFile(srcfp) 1318 if err != nil { 1319 return "", err 1320 } 1321 if err := ioutil.WriteFile(filepath.Join(dst, fd.Name()), contents, stat.Mode()); err != nil { 1322 return "", err 1323 } 1324 } 1325 return dst, nil 1326 } 1327 1328 func shouldSkip(data *Data, uri span.URI) bool { 1329 if data.ModfileFlagAvailable { 1330 return false 1331 } 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") { 1335 return true 1336 } 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\",") 1341 }