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