github.com/april1989/origin-go-tools@v0.0.32/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/april1989/origin-go-tools/go/expect" 27 "github.com/april1989/origin-go-tools/go/packages" 28 "github.com/april1989/origin-go-tools/go/packages/packagestest" 29 "github.com/april1989/origin-go-tools/internal/lsp/protocol" 30 "github.com/april1989/origin-go-tools/internal/lsp/source" 31 "github.com/april1989/origin-go-tools/internal/span" 32 "github.com/april1989/origin-go-tools/internal/testenv" 33 "github.com/april1989/origin-go-tools/txtar" 34 ) 35 36 const ( 37 overlayFileSuffix = ".overlay" 38 goldenFileSuffix = ".golden" 39 inFileSuffix = ".in" 40 summaryFile = "summary.txt" 41 testModule = "github.com/april1989/origin-go-tools/internal/lsp" 42 ) 43 44 var UpdateGolden = flag.Bool("golden", false, "Update golden files") 45 46 type CallHierarchy map[span.Span]*CallHierarchyResult 47 type CodeLens map[span.URI][]protocol.CodeLens 48 type Diagnostics map[span.URI][]*source.Diagnostic 49 type CompletionItems map[token.Pos]*source.CompletionItem 50 type Completions map[span.Span][]Completion 51 type CompletionSnippets map[span.Span][]CompletionSnippet 52 type UnimportedCompletions map[span.Span][]Completion 53 type DeepCompletions map[span.Span][]Completion 54 type FuzzyCompletions map[span.Span][]Completion 55 type CaseSensitiveCompletions map[span.Span][]Completion 56 type RankCompletions map[span.Span][]Completion 57 type FoldingRanges []span.Span 58 type Formats []span.Span 59 type Imports []span.Span 60 type SuggestedFixes map[span.Span][]string 61 type FunctionExtractions map[span.Span]span.Span 62 type Definitions map[span.Span]Definition 63 type Implementations map[span.Span][]span.Span 64 type Highlights map[span.Span][]span.Span 65 type References map[span.Span][]span.Span 66 type Renames map[span.Span]string 67 type PrepareRenames map[span.Span]*source.PrepareItem 68 type Symbols map[span.URI][]protocol.DocumentSymbol 69 type SymbolsChildren map[string][]protocol.DocumentSymbol 70 type SymbolInformation map[span.Span]protocol.SymbolInformation 71 type WorkspaceSymbols map[string][]protocol.SymbolInformation 72 type Signatures map[span.Span]*protocol.SignatureHelp 73 type Links map[span.URI][]Link 74 75 type Data struct { 76 Config packages.Config 77 Exported *packagestest.Exported 78 CallHierarchy CallHierarchy 79 CodeLens CodeLens 80 Diagnostics Diagnostics 81 CompletionItems CompletionItems 82 Completions Completions 83 CompletionSnippets CompletionSnippets 84 UnimportedCompletions UnimportedCompletions 85 DeepCompletions DeepCompletions 86 FuzzyCompletions FuzzyCompletions 87 CaseSensitiveCompletions CaseSensitiveCompletions 88 RankCompletions RankCompletions 89 FoldingRanges FoldingRanges 90 Formats Formats 91 Imports Imports 92 SuggestedFixes SuggestedFixes 93 FunctionExtractions FunctionExtractions 94 Definitions Definitions 95 Implementations Implementations 96 Highlights Highlights 97 References References 98 Renames Renames 99 PrepareRenames PrepareRenames 100 Symbols Symbols 101 symbolsChildren SymbolsChildren 102 symbolInformation SymbolInformation 103 WorkspaceSymbols WorkspaceSymbols 104 FuzzyWorkspaceSymbols WorkspaceSymbols 105 CaseSensitiveWorkspaceSymbols WorkspaceSymbols 106 Signatures Signatures 107 Links Links 108 109 t testing.TB 110 fragments map[string]string 111 dir string 112 Folder string 113 golden map[string]*Golden 114 115 ModfileFlagAvailable bool 116 117 mappersMu sync.Mutex 118 mappers map[span.URI]*protocol.ColumnMapper 119 } 120 121 type Tests interface { 122 CallHierarchy(*testing.T, span.Span, *CallHierarchyResult) 123 CodeLens(*testing.T, span.URI, []protocol.CodeLens) 124 Diagnostics(*testing.T, span.URI, []*source.Diagnostic) 125 Completion(*testing.T, span.Span, Completion, CompletionItems) 126 CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems) 127 UnimportedCompletion(*testing.T, span.Span, Completion, CompletionItems) 128 DeepCompletion(*testing.T, span.Span, Completion, CompletionItems) 129 FuzzyCompletion(*testing.T, span.Span, Completion, CompletionItems) 130 CaseSensitiveCompletion(*testing.T, span.Span, Completion, CompletionItems) 131 RankCompletion(*testing.T, span.Span, Completion, CompletionItems) 132 FoldingRanges(*testing.T, span.Span) 133 Format(*testing.T, span.Span) 134 Import(*testing.T, span.Span) 135 SuggestedFix(*testing.T, span.Span, []string) 136 FunctionExtraction(*testing.T, span.Span, span.Span) 137 Definition(*testing.T, span.Span, Definition) 138 Implementation(*testing.T, span.Span, []span.Span) 139 Highlight(*testing.T, span.Span, []span.Span) 140 References(*testing.T, span.Span, []span.Span) 141 Rename(*testing.T, span.Span, string) 142 PrepareRename(*testing.T, span.Span, *source.PrepareItem) 143 Symbols(*testing.T, span.URI, []protocol.DocumentSymbol) 144 WorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) 145 FuzzyWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) 146 CaseSensitiveWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) 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() source.Options { 224 o := source.DefaultOptions() 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.EnabledCodeLens[source.CommandTest.Name] = true 239 o.HoverKind = source.SynopsisDocumentation 240 o.InsertTextFormat = protocol.SnippetTextFormat 241 o.CompletionBudget = time.Minute 242 o.HierarchicalDocumentSymbolSupport = true 243 return o 244 } 245 246 var ( 247 go115 = false 248 ) 249 250 // Load creates the folder structure required when golibexec_testing with modules. 251 // The directory structure of a test needs to look like the example below: 252 // 253 // - dir 254 // - primarymod 255 // - .go files 256 // - packages 257 // - go.mod (optional) 258 // - modules 259 // - repoa 260 // - mod1 261 // - .go files 262 // - packages 263 // - go.mod (optional) 264 // - mod2 265 // - repob 266 // - mod1 267 // 268 // All the files that are primarily being tested should be in the primarymod folder, 269 // any auxillary packages should be declared in the modules folder. 270 // The modules folder requires each module to have the following format: repo/module 271 // Then inside each repo/module, there can be any number of packages and files that are 272 // needed to test the primarymod. 273 func Load(t testing.TB, exporter packagestest.Exporter, dir string) []*Data { 274 t.Helper() 275 276 folders, err := testFolders(dir) 277 if err != nil { 278 t.Fatalf("could not get test folders for %v, %v", dir, err) 279 } 280 281 var data []*Data 282 for _, folder := range folders { 283 datum := &Data{ 284 CallHierarchy: make(CallHierarchy), 285 CodeLens: make(CodeLens), 286 Diagnostics: make(Diagnostics), 287 CompletionItems: make(CompletionItems), 288 Completions: make(Completions), 289 CompletionSnippets: make(CompletionSnippets), 290 UnimportedCompletions: make(UnimportedCompletions), 291 DeepCompletions: make(DeepCompletions), 292 FuzzyCompletions: make(FuzzyCompletions), 293 RankCompletions: make(RankCompletions), 294 CaseSensitiveCompletions: make(CaseSensitiveCompletions), 295 Definitions: make(Definitions), 296 Implementations: make(Implementations), 297 Highlights: make(Highlights), 298 References: make(References), 299 Renames: make(Renames), 300 PrepareRenames: make(PrepareRenames), 301 SuggestedFixes: make(SuggestedFixes), 302 FunctionExtractions: make(FunctionExtractions), 303 Symbols: make(Symbols), 304 symbolsChildren: make(SymbolsChildren), 305 symbolInformation: make(SymbolInformation), 306 WorkspaceSymbols: make(WorkspaceSymbols), 307 FuzzyWorkspaceSymbols: make(WorkspaceSymbols), 308 CaseSensitiveWorkspaceSymbols: make(WorkspaceSymbols), 309 Signatures: make(Signatures), 310 Links: make(Links), 311 312 t: t, 313 dir: folder, 314 Folder: folder, 315 fragments: map[string]string{}, 316 golden: map[string]*Golden{}, 317 mappers: map[span.URI]*protocol.ColumnMapper{}, 318 } 319 320 if !*UpdateGolden { 321 summary := filepath.Join(filepath.FromSlash(folder), summaryFile+goldenFileSuffix) 322 if _, err := os.Stat(summary); os.IsNotExist(err) { 323 t.Fatalf("could not find golden file summary.txt in %#v", folder) 324 } 325 archive, err := txtar.ParseFile(summary) 326 if err != nil { 327 t.Fatalf("could not read golden file %v/%v: %v", folder, summary, err) 328 } 329 datum.golden[summaryFile] = &Golden{ 330 Filename: summary, 331 Archive: archive, 332 } 333 } 334 335 modules, _ := packagestest.GroupFilesByModules(folder) 336 for i, m := range modules { 337 for fragment, operation := range m.Files { 338 if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment { 339 delete(m.Files, fragment) 340 goldFile := filepath.Join(m.Name, fragment) 341 if i == 0 { 342 goldFile = filepath.Join(m.Name, "primarymod", fragment) 343 } 344 archive, err := txtar.ParseFile(goldFile) 345 if err != nil { 346 t.Fatalf("could not read golden file %v: %v", fragment, err) 347 } 348 datum.golden[trimmed] = &Golden{ 349 Filename: goldFile, 350 Archive: archive, 351 } 352 } else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment { 353 delete(m.Files, fragment) 354 m.Files[trimmed] = operation 355 } else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 { 356 delete(m.Files, fragment) 357 partial := fragment[:index] + fragment[index+len(overlayFileSuffix):] 358 overlayFile := filepath.Join(m.Name, fragment) 359 if i == 0 { 360 overlayFile = filepath.Join(m.Name, "primarymod", fragment) 361 } 362 contents, err := ioutil.ReadFile(overlayFile) 363 if err != nil { 364 t.Fatal(err) 365 } 366 m.Overlay[partial] = contents 367 } 368 } 369 } 370 if len(modules) > 0 { 371 // For certain LSP related tests to run, make sure that the primary 372 // module for the passed in directory is testModule. 373 modules[0].Name = testModule 374 } 375 // Add exampleModule to provide tests with another pkg. 376 datum.Exported = packagestest.Export(t, exporter, modules) 377 for _, m := range modules { 378 for fragment := range m.Files { 379 filename := datum.Exported.File(m.Name, fragment) 380 datum.fragments[filename] = fragment 381 } 382 } 383 384 // Turn off go/packages debug logging. 385 datum.Exported.Config.Logf = nil 386 datum.Config.Logf = nil 387 388 // Merge the exported.Config with the view.Config. 389 datum.Config = *datum.Exported.Config 390 datum.Config.Fset = token.NewFileSet() 391 datum.Config.Context = Context(nil) 392 datum.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { 393 panic("ParseFile should not be called") 394 } 395 396 // Do a first pass to collect special markers for completion and workspace symbols. 397 if err := datum.Exported.Expect(map[string]interface{}{ 398 "item": func(name string, r packagestest.Range, _ []string) { 399 datum.Exported.Mark(name, r) 400 }, 401 "symbol": func(name string, r packagestest.Range, _ []string) { 402 datum.Exported.Mark(name, r) 403 }, 404 }); err != nil { 405 t.Fatal(err) 406 } 407 408 // Collect any data that needs to be used by subsequent tests. 409 if err := datum.Exported.Expect(map[string]interface{}{ 410 "codelens": datum.collectCodeLens, 411 "diag": datum.collectDiagnostics, 412 "item": datum.collectCompletionItems, 413 "complete": datum.collectCompletions(CompletionDefault), 414 "unimported": datum.collectCompletions(CompletionUnimported), 415 "deep": datum.collectCompletions(CompletionDeep), 416 "fuzzy": datum.collectCompletions(CompletionFuzzy), 417 "casesensitive": datum.collectCompletions(CompletionCaseSensitive), 418 "rank": datum.collectCompletions(CompletionRank), 419 "snippet": datum.collectCompletionSnippets, 420 "fold": datum.collectFoldingRanges, 421 "format": datum.collectFormats, 422 "import": datum.collectImports, 423 "godef": datum.collectDefinitions, 424 "implementations": datum.collectImplementations, 425 "typdef": datum.collectTypeDefinitions, 426 "hover": datum.collectHoverDefinitions, 427 "highlight": datum.collectHighlights, 428 "refs": datum.collectReferences, 429 "rename": datum.collectRenames, 430 "prepare": datum.collectPrepareRenames, 431 "symbol": datum.collectSymbols, 432 "signature": datum.collectSignatures, 433 "link": datum.collectLinks, 434 "suggestedfix": datum.collectSuggestedFixes, 435 "extractfunc": datum.collectFunctionExtractions, 436 "incomingcalls": datum.collectIncomingCalls, 437 "outgoingcalls": datum.collectOutgoingCalls, 438 }); err != nil { 439 t.Fatal(err) 440 } 441 for _, symbols := range datum.Symbols { 442 for i := range symbols { 443 children := datum.symbolsChildren[symbols[i].Name] 444 symbols[i].Children = children 445 } 446 } 447 // Collect names for the entries that require golden files. 448 if err := datum.Exported.Expect(map[string]interface{}{ 449 "godef": datum.collectDefinitionNames, 450 "hover": datum.collectDefinitionNames, 451 "workspacesymbol": datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault), 452 "workspacesymbolfuzzy": datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy), 453 "workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive), 454 }); err != nil { 455 t.Fatal(err) 456 } 457 data = append(data, datum) 458 } 459 return data 460 } 461 462 func Run(t *testing.T, tests Tests, data *Data) { 463 t.Helper() 464 checkData(t, data) 465 466 eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) { 467 t.Helper() 468 469 for src, exp := range cases { 470 for i, e := range exp { 471 t.Run(SpanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) { 472 t.Helper() 473 if strings.Contains(t.Name(), "cgo") { 474 testenv.NeedsTool(t, "cgo") 475 } 476 if !go115 && strings.Contains(t.Name(), "declarecgo") { 477 t.Skip("test requires Go 1.15") 478 } 479 test(t, src, e, data.CompletionItems) 480 }) 481 } 482 483 } 484 } 485 486 eachWorkspaceSymbols := func(t *testing.T, cases map[string][]protocol.SymbolInformation, test func(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})) { 487 t.Helper() 488 489 for query, expectedSymbols := range cases { 490 name := query 491 if name == "" { 492 name = "EmptyQuery" 493 } 494 t.Run(name, func(t *testing.T) { 495 t.Helper() 496 dirs := make(map[string]struct{}) 497 for _, si := range expectedSymbols { 498 d := filepath.Dir(si.Location.URI.SpanURI().Filename()) 499 if _, ok := dirs[d]; !ok { 500 dirs[d] = struct{}{} 501 } 502 } 503 test(t, query, expectedSymbols, dirs) 504 }) 505 } 506 } 507 508 t.Run("CallHierarchy", func(t *testing.T) { 509 t.Helper() 510 for spn, callHierarchyResult := range data.CallHierarchy { 511 t.Run(SpanName(spn), func(t *testing.T) { 512 t.Helper() 513 tests.CallHierarchy(t, spn, callHierarchyResult) 514 }) 515 } 516 }) 517 518 t.Run("Completion", func(t *testing.T) { 519 t.Helper() 520 eachCompletion(t, data.Completions, tests.Completion) 521 }) 522 523 t.Run("CompletionSnippets", func(t *testing.T) { 524 t.Helper() 525 for _, placeholders := range []bool{true, false} { 526 for src, expecteds := range data.CompletionSnippets { 527 for i, expected := range expecteds { 528 name := SpanName(src) + "_" + strconv.Itoa(i+1) 529 if placeholders { 530 name += "_placeholders" 531 } 532 533 t.Run(name, func(t *testing.T) { 534 t.Helper() 535 tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems) 536 }) 537 } 538 } 539 } 540 }) 541 542 t.Run("UnimportedCompletion", func(t *testing.T) { 543 t.Helper() 544 eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion) 545 }) 546 547 t.Run("DeepCompletion", func(t *testing.T) { 548 t.Helper() 549 eachCompletion(t, data.DeepCompletions, tests.DeepCompletion) 550 }) 551 552 t.Run("FuzzyCompletion", func(t *testing.T) { 553 t.Helper() 554 eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion) 555 }) 556 557 t.Run("CaseSensitiveCompletion", func(t *testing.T) { 558 t.Helper() 559 eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion) 560 }) 561 562 t.Run("RankCompletions", func(t *testing.T) { 563 t.Helper() 564 eachCompletion(t, data.RankCompletions, tests.RankCompletion) 565 }) 566 567 t.Run("CodeLens", func(t *testing.T) { 568 t.Helper() 569 for uri, want := range data.CodeLens { 570 // Check if we should skip this URI if the -modfile flag is not available. 571 if shouldSkip(data, uri) { 572 continue 573 } 574 t.Run(uriName(uri), func(t *testing.T) { 575 t.Helper() 576 tests.CodeLens(t, uri, want) 577 }) 578 } 579 }) 580 581 t.Run("Diagnostics", func(t *testing.T) { 582 t.Helper() 583 for uri, want := range data.Diagnostics { 584 // Check if we should skip this URI if the -modfile flag is not available. 585 if shouldSkip(data, uri) { 586 continue 587 } 588 t.Run(uriName(uri), func(t *testing.T) { 589 t.Helper() 590 tests.Diagnostics(t, uri, want) 591 }) 592 } 593 }) 594 595 t.Run("FoldingRange", func(t *testing.T) { 596 t.Helper() 597 for _, spn := range data.FoldingRanges { 598 t.Run(uriName(spn.URI()), func(t *testing.T) { 599 t.Helper() 600 tests.FoldingRanges(t, spn) 601 }) 602 } 603 }) 604 605 t.Run("Format", func(t *testing.T) { 606 t.Helper() 607 for _, spn := range data.Formats { 608 t.Run(uriName(spn.URI()), func(t *testing.T) { 609 t.Helper() 610 tests.Format(t, spn) 611 }) 612 } 613 }) 614 615 t.Run("Import", func(t *testing.T) { 616 t.Helper() 617 for _, spn := range data.Imports { 618 t.Run(uriName(spn.URI()), func(t *testing.T) { 619 t.Helper() 620 tests.Import(t, spn) 621 }) 622 } 623 }) 624 625 t.Run("SuggestedFix", func(t *testing.T) { 626 t.Helper() 627 for spn, actionKinds := range data.SuggestedFixes { 628 // Check if we should skip this spn if the -modfile flag is not available. 629 if shouldSkip(data, spn.URI()) { 630 continue 631 } 632 t.Run(SpanName(spn), func(t *testing.T) { 633 t.Helper() 634 tests.SuggestedFix(t, spn, actionKinds) 635 }) 636 } 637 }) 638 639 t.Run("FunctionExtraction", func(t *testing.T) { 640 t.Helper() 641 for start, end := range data.FunctionExtractions { 642 // Check if we should skip this spn if the -modfile flag is not available. 643 if shouldSkip(data, start.URI()) { 644 continue 645 } 646 t.Run(SpanName(start), func(t *testing.T) { 647 t.Helper() 648 tests.FunctionExtraction(t, start, end) 649 }) 650 } 651 }) 652 653 t.Run("Definition", func(t *testing.T) { 654 t.Helper() 655 for spn, d := range data.Definitions { 656 t.Run(SpanName(spn), func(t *testing.T) { 657 t.Helper() 658 if strings.Contains(t.Name(), "cgo") { 659 testenv.NeedsTool(t, "cgo") 660 } 661 if !go115 && strings.Contains(t.Name(), "declarecgo") { 662 t.Skip("test requires Go 1.15") 663 } 664 tests.Definition(t, spn, d) 665 }) 666 } 667 }) 668 669 t.Run("Implementation", func(t *testing.T) { 670 t.Helper() 671 for spn, m := range data.Implementations { 672 t.Run(SpanName(spn), func(t *testing.T) { 673 t.Helper() 674 tests.Implementation(t, spn, m) 675 }) 676 } 677 }) 678 679 t.Run("Highlight", func(t *testing.T) { 680 t.Helper() 681 for pos, locations := range data.Highlights { 682 t.Run(SpanName(pos), func(t *testing.T) { 683 t.Helper() 684 tests.Highlight(t, pos, locations) 685 }) 686 } 687 }) 688 689 t.Run("References", func(t *testing.T) { 690 t.Helper() 691 for src, itemList := range data.References { 692 t.Run(SpanName(src), func(t *testing.T) { 693 t.Helper() 694 tests.References(t, src, itemList) 695 }) 696 } 697 }) 698 699 t.Run("Renames", func(t *testing.T) { 700 t.Helper() 701 for spn, newText := range data.Renames { 702 t.Run(uriName(spn.URI())+"_"+newText, func(t *testing.T) { 703 t.Helper() 704 tests.Rename(t, spn, newText) 705 }) 706 } 707 }) 708 709 t.Run("PrepareRenames", func(t *testing.T) { 710 t.Helper() 711 for src, want := range data.PrepareRenames { 712 t.Run(SpanName(src), func(t *testing.T) { 713 t.Helper() 714 tests.PrepareRename(t, src, want) 715 }) 716 } 717 }) 718 719 t.Run("Symbols", func(t *testing.T) { 720 t.Helper() 721 for uri, expectedSymbols := range data.Symbols { 722 t.Run(uriName(uri), func(t *testing.T) { 723 t.Helper() 724 tests.Symbols(t, uri, expectedSymbols) 725 }) 726 } 727 }) 728 729 t.Run("WorkspaceSymbols", func(t *testing.T) { 730 t.Helper() 731 eachWorkspaceSymbols(t, data.WorkspaceSymbols, tests.WorkspaceSymbols) 732 }) 733 734 t.Run("FuzzyWorkspaceSymbols", func(t *testing.T) { 735 t.Helper() 736 eachWorkspaceSymbols(t, data.FuzzyWorkspaceSymbols, tests.FuzzyWorkspaceSymbols) 737 }) 738 739 t.Run("CaseSensitiveWorkspaceSymbols", func(t *testing.T) { 740 t.Helper() 741 eachWorkspaceSymbols(t, data.CaseSensitiveWorkspaceSymbols, tests.CaseSensitiveWorkspaceSymbols) 742 }) 743 744 t.Run("SignatureHelp", func(t *testing.T) { 745 t.Helper() 746 for spn, expectedSignature := range data.Signatures { 747 t.Run(SpanName(spn), func(t *testing.T) { 748 t.Helper() 749 tests.SignatureHelp(t, spn, expectedSignature) 750 }) 751 } 752 }) 753 754 t.Run("Link", func(t *testing.T) { 755 t.Helper() 756 for uri, wantLinks := range data.Links { 757 // If we are golibexec_testing GOPATH, then we do not want links with 758 // the versions attached (pkg.go.dev/repoa/moda@v1.1.0/pkg), 759 // unless the file is a go.mod, then we can skip it alltogether. 760 if data.Exported.Exporter == packagestest.GOPATH { 761 if strings.HasSuffix(uri.Filename(), ".mod") { 762 continue 763 } 764 re := regexp.MustCompile(`@v\d+\.\d+\.[\w-]+`) 765 for i, link := range wantLinks { 766 wantLinks[i].Target = re.ReplaceAllString(link.Target, "") 767 } 768 } 769 t.Run(uriName(uri), func(t *testing.T) { 770 t.Helper() 771 tests.Link(t, uri, wantLinks) 772 }) 773 } 774 }) 775 776 if *UpdateGolden { 777 for _, golden := range data.golden { 778 if !golden.Modified { 779 continue 780 } 781 sort.Slice(golden.Archive.Files, func(i, j int) bool { 782 return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name 783 }) 784 if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil { 785 t.Fatal(err) 786 } 787 } 788 } 789 } 790 791 func checkData(t *testing.T, data *Data) { 792 buf := &bytes.Buffer{} 793 diagnosticsCount := 0 794 for _, want := range data.Diagnostics { 795 diagnosticsCount += len(want) 796 } 797 linksCount := 0 798 for _, want := range data.Links { 799 linksCount += len(want) 800 } 801 definitionCount := 0 802 typeDefinitionCount := 0 803 for _, d := range data.Definitions { 804 if d.IsType { 805 typeDefinitionCount++ 806 } else { 807 definitionCount++ 808 } 809 } 810 811 snippetCount := 0 812 for _, want := range data.CompletionSnippets { 813 snippetCount += len(want) 814 } 815 816 countCompletions := func(c map[span.Span][]Completion) (count int) { 817 for _, want := range c { 818 count += len(want) 819 } 820 return count 821 } 822 823 countCodeLens := func(c map[span.URI][]protocol.CodeLens) (count int) { 824 for _, want := range c { 825 count += len(want) 826 } 827 return count 828 } 829 830 fmt.Fprintf(buf, "CallHierarchyCount = %v\n", len(data.CallHierarchy)) 831 fmt.Fprintf(buf, "CodeLensCount = %v\n", countCodeLens(data.CodeLens)) 832 fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions)) 833 fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount) 834 fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions)) 835 fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", countCompletions(data.DeepCompletions)) 836 fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", countCompletions(data.FuzzyCompletions)) 837 fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", countCompletions(data.RankCompletions)) 838 fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", countCompletions(data.CaseSensitiveCompletions)) 839 fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount) 840 fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges)) 841 fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats)) 842 fmt.Fprintf(buf, "ImportCount = %v\n", len(data.Imports)) 843 fmt.Fprintf(buf, "SuggestedFixCount = %v\n", len(data.SuggestedFixes)) 844 fmt.Fprintf(buf, "FunctionExtractionCount = %v\n", len(data.FunctionExtractions)) 845 fmt.Fprintf(buf, "DefinitionsCount = %v\n", definitionCount) 846 fmt.Fprintf(buf, "TypeDefinitionsCount = %v\n", typeDefinitionCount) 847 fmt.Fprintf(buf, "HighlightsCount = %v\n", len(data.Highlights)) 848 fmt.Fprintf(buf, "ReferencesCount = %v\n", len(data.References)) 849 fmt.Fprintf(buf, "RenamesCount = %v\n", len(data.Renames)) 850 fmt.Fprintf(buf, "PrepareRenamesCount = %v\n", len(data.PrepareRenames)) 851 fmt.Fprintf(buf, "SymbolsCount = %v\n", len(data.Symbols)) 852 fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", len(data.WorkspaceSymbols)) 853 fmt.Fprintf(buf, "FuzzyWorkspaceSymbolsCount = %v\n", len(data.FuzzyWorkspaceSymbols)) 854 fmt.Fprintf(buf, "CaseSensitiveWorkspaceSymbolsCount = %v\n", len(data.CaseSensitiveWorkspaceSymbols)) 855 fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures)) 856 fmt.Fprintf(buf, "LinksCount = %v\n", linksCount) 857 fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations)) 858 859 want := string(data.Golden("summary", summaryFile, func() ([]byte, error) { 860 return buf.Bytes(), nil 861 })) 862 got := buf.String() 863 if want != got { 864 t.Errorf("test summary does not match:\n%s", Diff(want, got)) 865 } 866 } 867 868 func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) { 869 data.mappersMu.Lock() 870 defer data.mappersMu.Unlock() 871 872 if _, ok := data.mappers[uri]; !ok { 873 content, err := data.Exported.FileContents(uri.Filename()) 874 if err != nil { 875 return nil, err 876 } 877 converter := span.NewContentConverter(uri.Filename(), content) 878 data.mappers[uri] = &protocol.ColumnMapper{ 879 URI: uri, 880 Converter: converter, 881 Content: content, 882 } 883 } 884 return data.mappers[uri], nil 885 } 886 887 func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte { 888 data.t.Helper() 889 fragment, found := data.fragments[target] 890 if !found { 891 if filepath.IsAbs(target) { 892 data.t.Fatalf("invalid golden file fragment %v", target) 893 } 894 fragment = target 895 } 896 golden := data.golden[fragment] 897 if golden == nil { 898 if !*UpdateGolden { 899 data.t.Fatalf("could not find golden file %v: %v", fragment, tag) 900 } 901 var subdir string 902 if fragment != summaryFile { 903 subdir = "primarymod" 904 } 905 golden = &Golden{ 906 Filename: filepath.Join(data.dir, subdir, fragment+goldenFileSuffix), 907 Archive: &txtar.Archive{}, 908 Modified: true, 909 } 910 data.golden[fragment] = golden 911 } 912 var file *txtar.File 913 for i := range golden.Archive.Files { 914 f := &golden.Archive.Files[i] 915 if f.Name == tag { 916 file = f 917 break 918 } 919 } 920 if *UpdateGolden { 921 if file == nil { 922 golden.Archive.Files = append(golden.Archive.Files, txtar.File{ 923 Name: tag, 924 }) 925 file = &golden.Archive.Files[len(golden.Archive.Files)-1] 926 } 927 contents, err := update() 928 if err != nil { 929 data.t.Fatalf("could not update golden file %v: %v", fragment, err) 930 } 931 file.Data = append(contents, '\n') // add trailing \n for txtar 932 golden.Modified = true 933 934 } 935 if file == nil { 936 data.t.Fatalf("could not find golden contents %v: %v", fragment, tag) 937 } 938 return file.Data[:len(file.Data)-1] // drop the trailing \n 939 } 940 941 func (data *Data) collectCodeLens(spn span.Span, title, cmd string) { 942 if _, ok := data.CodeLens[spn.URI()]; !ok { 943 data.CodeLens[spn.URI()] = []protocol.CodeLens{} 944 } 945 m, err := data.Mapper(spn.URI()) 946 if err != nil { 947 return 948 } 949 rng, err := m.Range(spn) 950 if err != nil { 951 return 952 } 953 data.CodeLens[spn.URI()] = append(data.CodeLens[spn.URI()], protocol.CodeLens{ 954 Range: rng, 955 Command: protocol.Command{ 956 Title: title, 957 Command: cmd, 958 }, 959 }) 960 } 961 962 func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg, msgSeverity string) { 963 if _, ok := data.Diagnostics[spn.URI()]; !ok { 964 data.Diagnostics[spn.URI()] = []*source.Diagnostic{} 965 } 966 m, err := data.Mapper(spn.URI()) 967 if err != nil { 968 return 969 } 970 rng, err := m.Range(spn) 971 if err != nil { 972 return 973 } 974 severity := protocol.SeverityError 975 switch msgSeverity { 976 case "error": 977 severity = protocol.SeverityError 978 case "warning": 979 severity = protocol.SeverityWarning 980 case "hint": 981 severity = protocol.SeverityHint 982 case "information": 983 severity = protocol.SeverityInformation 984 } 985 // This is not the correct way to do this, but it seems excessive to do the full conversion here. 986 want := &source.Diagnostic{ 987 Range: rng, 988 Severity: severity, 989 Source: msgSource, 990 Message: msg, 991 } 992 data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want) 993 } 994 995 func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) { 996 result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) { 997 m[src] = append(m[src], Completion{ 998 CompletionItems: expected, 999 }) 1000 } 1001 switch typ { 1002 case CompletionDeep: 1003 return func(src span.Span, expected []token.Pos) { 1004 result(data.DeepCompletions, src, expected) 1005 } 1006 case CompletionUnimported: 1007 return func(src span.Span, expected []token.Pos) { 1008 result(data.UnimportedCompletions, src, expected) 1009 } 1010 case CompletionFuzzy: 1011 return func(src span.Span, expected []token.Pos) { 1012 result(data.FuzzyCompletions, src, expected) 1013 } 1014 case CompletionRank: 1015 return func(src span.Span, expected []token.Pos) { 1016 result(data.RankCompletions, src, expected) 1017 } 1018 case CompletionCaseSensitive: 1019 return func(src span.Span, expected []token.Pos) { 1020 result(data.CaseSensitiveCompletions, src, expected) 1021 } 1022 default: 1023 return func(src span.Span, expected []token.Pos) { 1024 result(data.Completions, src, expected) 1025 } 1026 } 1027 } 1028 1029 func (data *Data) collectCompletionItems(pos token.Pos, args []string) { 1030 if len(args) < 3 { 1031 loc := data.Exported.ExpectFileSet.Position(pos) 1032 data.t.Fatalf("%s:%d: @item expects at least 3 args, got %d", 1033 loc.Filename, loc.Line, len(args)) 1034 } 1035 label, detail, kind := args[0], args[1], args[2] 1036 var documentation string 1037 if len(args) == 4 { 1038 documentation = args[3] 1039 } 1040 data.CompletionItems[pos] = &source.CompletionItem{ 1041 Label: label, 1042 Detail: detail, 1043 Kind: protocol.ParseCompletionItemKind(kind), 1044 Documentation: documentation, 1045 } 1046 } 1047 1048 func (data *Data) collectFoldingRanges(spn span.Span) { 1049 data.FoldingRanges = append(data.FoldingRanges, spn) 1050 } 1051 1052 func (data *Data) collectFormats(spn span.Span) { 1053 data.Formats = append(data.Formats, spn) 1054 } 1055 1056 func (data *Data) collectImports(spn span.Span) { 1057 data.Imports = append(data.Imports, spn) 1058 } 1059 1060 func (data *Data) collectSuggestedFixes(spn span.Span, actionKind string) { 1061 if _, ok := data.SuggestedFixes[spn]; !ok { 1062 data.SuggestedFixes[spn] = []string{} 1063 } 1064 data.SuggestedFixes[spn] = append(data.SuggestedFixes[spn], actionKind) 1065 } 1066 1067 func (data *Data) collectFunctionExtractions(start span.Span, end span.Span) { 1068 if _, ok := data.FunctionExtractions[start]; !ok { 1069 data.FunctionExtractions[start] = end 1070 } 1071 } 1072 1073 func (data *Data) collectDefinitions(src, target span.Span) { 1074 data.Definitions[src] = Definition{ 1075 Src: src, 1076 Def: target, 1077 } 1078 } 1079 1080 func (data *Data) collectImplementations(src span.Span, targets []span.Span) { 1081 data.Implementations[src] = targets 1082 } 1083 1084 func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) { 1085 for _, call := range calls { 1086 m, err := data.Mapper(call.URI()) 1087 if err != nil { 1088 data.t.Fatal(err) 1089 } 1090 rng, err := m.Range(call) 1091 if err != nil { 1092 data.t.Fatal(err) 1093 } 1094 // we're only comparing protocol.range 1095 if data.CallHierarchy[src] != nil { 1096 data.CallHierarchy[src].IncomingCalls = append(data.CallHierarchy[src].IncomingCalls, 1097 protocol.CallHierarchyItem{ 1098 URI: protocol.DocumentURI(call.URI()), 1099 Range: rng, 1100 }) 1101 } else { 1102 data.CallHierarchy[src] = &CallHierarchyResult{ 1103 IncomingCalls: []protocol.CallHierarchyItem{ 1104 {URI: protocol.DocumentURI(call.URI()), Range: rng}, 1105 }, 1106 } 1107 } 1108 } 1109 } 1110 1111 func (data *Data) collectOutgoingCalls(src span.Span, calls []span.Span) { 1112 for _, call := range calls { 1113 m, err := data.Mapper(call.URI()) 1114 if err != nil { 1115 data.t.Fatal(err) 1116 } 1117 rng, err := m.Range(call) 1118 if err != nil { 1119 data.t.Fatal(err) 1120 } 1121 // we're only comparing protocol.range 1122 if data.CallHierarchy[src] != nil { 1123 data.CallHierarchy[src].OutgoingCalls = append(data.CallHierarchy[src].OutgoingCalls, 1124 protocol.CallHierarchyItem{ 1125 URI: protocol.DocumentURI(call.URI()), 1126 Range: rng, 1127 }) 1128 } else { 1129 data.CallHierarchy[src] = &CallHierarchyResult{ 1130 OutgoingCalls: []protocol.CallHierarchyItem{ 1131 {URI: protocol.DocumentURI(call.URI()), Range: rng}, 1132 }, 1133 } 1134 } 1135 } 1136 } 1137 1138 func (data *Data) collectHoverDefinitions(src, target span.Span) { 1139 data.Definitions[src] = Definition{ 1140 Src: src, 1141 Def: target, 1142 OnlyHover: true, 1143 } 1144 } 1145 1146 func (data *Data) collectTypeDefinitions(src, target span.Span) { 1147 data.Definitions[src] = Definition{ 1148 Src: src, 1149 Def: target, 1150 IsType: true, 1151 } 1152 } 1153 1154 func (data *Data) collectDefinitionNames(src span.Span, name string) { 1155 d := data.Definitions[src] 1156 d.Name = name 1157 data.Definitions[src] = d 1158 } 1159 1160 func (data *Data) collectHighlights(src span.Span, expected []span.Span) { 1161 // Declaring a highlight in a test file: @highlight(src, expected1, expected2) 1162 data.Highlights[src] = append(data.Highlights[src], expected...) 1163 } 1164 1165 func (data *Data) collectReferences(src span.Span, expected []span.Span) { 1166 data.References[src] = expected 1167 } 1168 1169 func (data *Data) collectRenames(src span.Span, newText string) { 1170 data.Renames[src] = newText 1171 } 1172 1173 func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placeholder string) { 1174 m, err := data.Mapper(src.URI()) 1175 if err != nil { 1176 data.t.Fatal(err) 1177 } 1178 // Convert range to span and then to protocol.Range. 1179 spn, err := rng.Span() 1180 if err != nil { 1181 data.t.Fatal(err) 1182 } 1183 prng, err := m.Range(spn) 1184 if err != nil { 1185 data.t.Fatal(err) 1186 } 1187 data.PrepareRenames[src] = &source.PrepareItem{ 1188 Range: prng, 1189 Text: placeholder, 1190 } 1191 } 1192 1193 // collectSymbols is responsible for collecting @symbol annotations. 1194 func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string, siName string) { 1195 m, err := data.Mapper(spn.URI()) 1196 if err != nil { 1197 data.t.Fatal(err) 1198 } 1199 rng, err := m.Range(spn) 1200 if err != nil { 1201 data.t.Fatal(err) 1202 } 1203 sym := protocol.DocumentSymbol{ 1204 Name: name, 1205 Kind: protocol.ParseSymbolKind(kind), 1206 SelectionRange: rng, 1207 } 1208 if parentName == "" { 1209 data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym) 1210 } else { 1211 data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym) 1212 } 1213 1214 // Reuse @symbol in the workspace symbols tests. 1215 si := protocol.SymbolInformation{ 1216 Name: siName, 1217 Kind: sym.Kind, 1218 Location: protocol.Location{ 1219 URI: protocol.URIFromSpanURI(spn.URI()), 1220 Range: sym.SelectionRange, 1221 }, 1222 } 1223 data.symbolInformation[spn] = si 1224 } 1225 1226 func (data *Data) collectWorkspaceSymbols(typ WorkspaceSymbolsTestType) func(string, []span.Span) { 1227 switch typ { 1228 case WorkspaceSymbolsFuzzy: 1229 return func(query string, targets []span.Span) { 1230 data.FuzzyWorkspaceSymbols[query] = make([]protocol.SymbolInformation, 0, len(targets)) 1231 for _, target := range targets { 1232 data.FuzzyWorkspaceSymbols[query] = append(data.FuzzyWorkspaceSymbols[query], data.symbolInformation[target]) 1233 } 1234 } 1235 case WorkspaceSymbolsCaseSensitive: 1236 return func(query string, targets []span.Span) { 1237 data.CaseSensitiveWorkspaceSymbols[query] = make([]protocol.SymbolInformation, 0, len(targets)) 1238 for _, target := range targets { 1239 data.CaseSensitiveWorkspaceSymbols[query] = append(data.CaseSensitiveWorkspaceSymbols[query], data.symbolInformation[target]) 1240 } 1241 } 1242 default: 1243 return func(query string, targets []span.Span) { 1244 data.WorkspaceSymbols[query] = make([]protocol.SymbolInformation, 0, len(targets)) 1245 for _, target := range targets { 1246 data.WorkspaceSymbols[query] = append(data.WorkspaceSymbols[query], data.symbolInformation[target]) 1247 } 1248 } 1249 } 1250 } 1251 1252 func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) { 1253 data.Signatures[spn] = &protocol.SignatureHelp{ 1254 Signatures: []protocol.SignatureInformation{ 1255 { 1256 Label: signature, 1257 }, 1258 }, 1259 ActiveParameter: float64(activeParam), 1260 } 1261 // Hardcode special case to test the lack of a signature. 1262 if signature == "" && activeParam == 0 { 1263 data.Signatures[spn] = nil 1264 } 1265 } 1266 1267 func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) { 1268 data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{ 1269 CompletionItem: item, 1270 PlainSnippet: plain, 1271 PlaceholderSnippet: placeholder, 1272 }) 1273 } 1274 1275 func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) { 1276 position := fset.Position(note.Pos) 1277 uri := spn.URI() 1278 data.Links[uri] = append(data.Links[uri], Link{ 1279 Src: spn, 1280 Target: link, 1281 NotePosition: position, 1282 }) 1283 } 1284 1285 func uriName(uri span.URI) string { 1286 return filepath.Base(strings.TrimSuffix(uri.Filename(), ".go")) 1287 } 1288 1289 func SpanName(spn span.Span) string { 1290 return fmt.Sprintf("%v_%v_%v", uriName(spn.URI()), spn.Start().Line(), spn.Start().Column()) 1291 } 1292 1293 func CopyFolderToTempDir(folder string) (string, error) { 1294 if _, err := os.Stat(folder); err != nil { 1295 return "", err 1296 } 1297 dst, err := ioutil.TempDir("", "modfile_test") 1298 if err != nil { 1299 return "", err 1300 } 1301 fds, err := ioutil.ReadDir(folder) 1302 if err != nil { 1303 return "", err 1304 } 1305 for _, fd := range fds { 1306 srcfp := filepath.Join(folder, fd.Name()) 1307 stat, err := os.Stat(srcfp) 1308 if err != nil { 1309 return "", err 1310 } 1311 if !stat.Mode().IsRegular() { 1312 return "", fmt.Errorf("cannot copy non regular file %s", srcfp) 1313 } 1314 contents, err := ioutil.ReadFile(srcfp) 1315 if err != nil { 1316 return "", err 1317 } 1318 if err := ioutil.WriteFile(filepath.Join(dst, fd.Name()), contents, stat.Mode()); err != nil { 1319 return "", err 1320 } 1321 } 1322 return dst, nil 1323 } 1324 1325 func testFolders(root string) ([]string, error) { 1326 // Check if this only has one test directory. 1327 if _, err := os.Stat(filepath.Join(filepath.FromSlash(root), "primarymod")); !os.IsNotExist(err) { 1328 return []string{root}, nil 1329 } 1330 folders := []string{} 1331 root = filepath.FromSlash(root) 1332 // Get all test directories that are one level deeper than root. 1333 if err := filepath.Walk(root, func(path string, info os.FileInfo, _ error) error { 1334 if !info.IsDir() { 1335 return nil 1336 } 1337 if filepath.Dir(path) == root { 1338 folders = append(folders, filepath.ToSlash(path)) 1339 } 1340 return nil 1341 }); err != nil { 1342 return nil, err 1343 } 1344 return folders, nil 1345 } 1346 1347 func shouldSkip(data *Data, uri span.URI) bool { 1348 if data.ModfileFlagAvailable { 1349 return false 1350 } 1351 // If the -modfile flag is not available, then we do not want to run 1352 // any tests on the go.mod file. 1353 if strings.HasSuffix(uri.Filename(), ".mod") { 1354 return true 1355 } 1356 // If the -modfile flag is not available, then we do not want to test any 1357 // uri that contains "go mod tidy". 1358 m, err := data.Mapper(uri) 1359 return err == nil && strings.Contains(string(m.Content), ", \"go mod tidy\",") 1360 }