X-Git-Url: https://git.josue.xyz/?a=blobdiff_plain;f=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201105173854-bc9fc8d8c4bc%2Finternal%2Flsp%2Ftests%2Ftests.go;fp=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201105173854-bc9fc8d8c4bc%2Finternal%2Flsp%2Ftests%2Ftests.go;h=3d173b4dcd3ee85e7b19c1b3f16372e0332e9731;hb=4d07c77cf4d78cab8639e13ddc3c22495e585b0b;hp=0000000000000000000000000000000000000000;hpb=b3950616b54221c40a7dab9099bda675007e5b6e;p=dotfiles%2F.git diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/tests/tests.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/tests/tests.go new file mode 100644 index 00000000..3d173b4d --- /dev/null +++ b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/tests/tests.go @@ -0,0 +1,1381 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tests exports functionality to be used across a variety of gopls tests. +package tests + +import ( + "bytes" + "context" + "flag" + "fmt" + "go/ast" + "go/token" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/tools/go/expect" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/packages/packagestest" + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/lsp/source" + "golang.org/x/tools/internal/lsp/source/completion" + "golang.org/x/tools/internal/span" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/txtar" +) + +const ( + overlayFileSuffix = ".overlay" + goldenFileSuffix = ".golden" + inFileSuffix = ".in" + summaryFile = "summary.txt" + testModule = "golang.org/x/tools/internal/lsp" +) + +var UpdateGolden = flag.Bool("golden", false, "Update golden files") + +type CallHierarchy map[span.Span]*CallHierarchyResult +type CodeLens map[span.URI][]protocol.CodeLens +type Diagnostics map[span.URI][]*source.Diagnostic +type CompletionItems map[token.Pos]*completion.CompletionItem +type Completions map[span.Span][]Completion +type CompletionSnippets map[span.Span][]CompletionSnippet +type UnimportedCompletions map[span.Span][]Completion +type DeepCompletions map[span.Span][]Completion +type FuzzyCompletions map[span.Span][]Completion +type CaseSensitiveCompletions map[span.Span][]Completion +type RankCompletions map[span.Span][]Completion +type FoldingRanges []span.Span +type Formats []span.Span +type Imports []span.Span +type SemanticTokens []span.Span +type SuggestedFixes map[span.Span][]string +type FunctionExtractions map[span.Span]span.Span +type Definitions map[span.Span]Definition +type Implementations map[span.Span][]span.Span +type Highlights map[span.Span][]span.Span +type References map[span.Span][]span.Span +type Renames map[span.Span]string +type PrepareRenames map[span.Span]*source.PrepareItem +type Symbols map[span.URI][]protocol.DocumentSymbol +type SymbolsChildren map[string][]protocol.DocumentSymbol +type SymbolInformation map[span.Span]protocol.SymbolInformation +type WorkspaceSymbols map[string][]protocol.SymbolInformation +type Signatures map[span.Span]*protocol.SignatureHelp +type Links map[span.URI][]Link + +type Data struct { + Config packages.Config + Exported *packagestest.Exported + CallHierarchy CallHierarchy + CodeLens CodeLens + Diagnostics Diagnostics + CompletionItems CompletionItems + Completions Completions + CompletionSnippets CompletionSnippets + UnimportedCompletions UnimportedCompletions + DeepCompletions DeepCompletions + FuzzyCompletions FuzzyCompletions + CaseSensitiveCompletions CaseSensitiveCompletions + RankCompletions RankCompletions + FoldingRanges FoldingRanges + Formats Formats + Imports Imports + SemanticTokens SemanticTokens + SuggestedFixes SuggestedFixes + FunctionExtractions FunctionExtractions + Definitions Definitions + Implementations Implementations + Highlights Highlights + References References + Renames Renames + PrepareRenames PrepareRenames + Symbols Symbols + symbolsChildren SymbolsChildren + symbolInformation SymbolInformation + WorkspaceSymbols WorkspaceSymbols + FuzzyWorkspaceSymbols WorkspaceSymbols + CaseSensitiveWorkspaceSymbols WorkspaceSymbols + Signatures Signatures + Links Links + + t testing.TB + fragments map[string]string + dir string + golden map[string]*Golden + mode string + + ModfileFlagAvailable bool + + mappersMu sync.Mutex + mappers map[span.URI]*protocol.ColumnMapper +} + +type Tests interface { + CallHierarchy(*testing.T, span.Span, *CallHierarchyResult) + CodeLens(*testing.T, span.URI, []protocol.CodeLens) + Diagnostics(*testing.T, span.URI, []*source.Diagnostic) + Completion(*testing.T, span.Span, Completion, CompletionItems) + CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems) + UnimportedCompletion(*testing.T, span.Span, Completion, CompletionItems) + DeepCompletion(*testing.T, span.Span, Completion, CompletionItems) + FuzzyCompletion(*testing.T, span.Span, Completion, CompletionItems) + CaseSensitiveCompletion(*testing.T, span.Span, Completion, CompletionItems) + RankCompletion(*testing.T, span.Span, Completion, CompletionItems) + FoldingRanges(*testing.T, span.Span) + Format(*testing.T, span.Span) + Import(*testing.T, span.Span) + SemanticTokens(*testing.T, span.Span) + SuggestedFix(*testing.T, span.Span, []string) + FunctionExtraction(*testing.T, span.Span, span.Span) + Definition(*testing.T, span.Span, Definition) + Implementation(*testing.T, span.Span, []span.Span) + Highlight(*testing.T, span.Span, []span.Span) + References(*testing.T, span.Span, []span.Span) + Rename(*testing.T, span.Span, string) + PrepareRename(*testing.T, span.Span, *source.PrepareItem) + Symbols(*testing.T, span.URI, []protocol.DocumentSymbol) + WorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) + FuzzyWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) + CaseSensitiveWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) + SignatureHelp(*testing.T, span.Span, *protocol.SignatureHelp) + Link(*testing.T, span.URI, []Link) +} + +type Definition struct { + Name string + IsType bool + OnlyHover bool + Src, Def span.Span +} + +type CompletionTestType int + +const ( + // Default runs the standard completion tests. + CompletionDefault = CompletionTestType(iota) + + // Unimported tests the autocompletion of unimported packages. + CompletionUnimported + + // Deep tests deep completion. + CompletionDeep + + // Fuzzy tests deep completion and fuzzy matching. + CompletionFuzzy + + // CaseSensitive tests case sensitive completion. + CompletionCaseSensitive + + // CompletionRank candidates in test must be valid and in the right relative order. + CompletionRank +) + +type WorkspaceSymbolsTestType int + +const ( + // Default runs the standard workspace symbols tests. + WorkspaceSymbolsDefault = WorkspaceSymbolsTestType(iota) + + // Fuzzy tests workspace symbols with fuzzy matching. + WorkspaceSymbolsFuzzy + + // CaseSensitive tests workspace symbols with case sensitive. + WorkspaceSymbolsCaseSensitive +) + +type Completion struct { + CompletionItems []token.Pos +} + +type CompletionSnippet struct { + CompletionItem token.Pos + PlainSnippet string + PlaceholderSnippet string +} + +type CallHierarchyResult struct { + IncomingCalls, OutgoingCalls []protocol.CallHierarchyItem +} + +type Link struct { + Src span.Span + Target string + NotePosition token.Position +} + +type Golden struct { + Filename string + Archive *txtar.Archive + Modified bool +} + +func Context(t testing.TB) context.Context { + return context.Background() +} + +func DefaultOptions(o *source.Options) { + o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{ + source.Go: { + protocol.SourceOrganizeImports: true, + protocol.QuickFix: true, + protocol.RefactorRewrite: true, + protocol.RefactorExtract: true, + protocol.SourceFixAll: true, + }, + source.Mod: { + protocol.SourceOrganizeImports: true, + }, + source.Sum: {}, + } + o.ExperimentalOptions.Codelens[source.CommandTest.Name] = true + o.HoverKind = source.SynopsisDocumentation + o.InsertTextFormat = protocol.SnippetTextFormat + o.CompletionBudget = time.Minute + o.HierarchicalDocumentSymbolSupport = true + o.ExperimentalWorkspaceModule = true + o.SemanticTokens = true +} + +func RunTests(t *testing.T, dataDir string, includeMultiModule bool, f func(*testing.T, *Data)) { + t.Helper() + modes := []string{"Modules", "GOPATH"} + if includeMultiModule { + modes = append(modes, "MultiModule") + } + for _, mode := range modes { + t.Run(mode, func(t *testing.T) { + t.Helper() + if mode == "MultiModule" { + // Some bug in 1.12 breaks reading markers, and it's not worth figuring out. + testenv.NeedsGo1Point(t, 13) + } + datum := load(t, mode, dataDir) + f(t, datum) + }) + } +} + +func load(t testing.TB, mode string, dir string) *Data { + t.Helper() + + datum := &Data{ + CallHierarchy: make(CallHierarchy), + CodeLens: make(CodeLens), + Diagnostics: make(Diagnostics), + CompletionItems: make(CompletionItems), + Completions: make(Completions), + CompletionSnippets: make(CompletionSnippets), + UnimportedCompletions: make(UnimportedCompletions), + DeepCompletions: make(DeepCompletions), + FuzzyCompletions: make(FuzzyCompletions), + RankCompletions: make(RankCompletions), + CaseSensitiveCompletions: make(CaseSensitiveCompletions), + Definitions: make(Definitions), + Implementations: make(Implementations), + Highlights: make(Highlights), + References: make(References), + Renames: make(Renames), + PrepareRenames: make(PrepareRenames), + SuggestedFixes: make(SuggestedFixes), + FunctionExtractions: make(FunctionExtractions), + Symbols: make(Symbols), + symbolsChildren: make(SymbolsChildren), + symbolInformation: make(SymbolInformation), + WorkspaceSymbols: make(WorkspaceSymbols), + FuzzyWorkspaceSymbols: make(WorkspaceSymbols), + CaseSensitiveWorkspaceSymbols: make(WorkspaceSymbols), + Signatures: make(Signatures), + Links: make(Links), + + t: t, + dir: dir, + fragments: map[string]string{}, + golden: map[string]*Golden{}, + mode: mode, + mappers: map[span.URI]*protocol.ColumnMapper{}, + } + + if !*UpdateGolden { + summary := filepath.Join(filepath.FromSlash(dir), summaryFile+goldenFileSuffix) + if _, err := os.Stat(summary); os.IsNotExist(err) { + t.Fatalf("could not find golden file summary.txt in %#v", dir) + } + archive, err := txtar.ParseFile(summary) + if err != nil { + t.Fatalf("could not read golden file %v/%v: %v", dir, summary, err) + } + datum.golden[summaryFile] = &Golden{ + Filename: summary, + Archive: archive, + } + } + + files := packagestest.MustCopyFileTree(dir) + overlays := map[string][]byte{} + for fragment, operation := range files { + if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment { + delete(files, fragment) + goldFile := filepath.Join(dir, fragment) + archive, err := txtar.ParseFile(goldFile) + if err != nil { + t.Fatalf("could not read golden file %v: %v", fragment, err) + } + datum.golden[trimmed] = &Golden{ + Filename: goldFile, + Archive: archive, + } + } else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment { + delete(files, fragment) + files[trimmed] = operation + } else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 { + delete(files, fragment) + partial := fragment[:index] + fragment[index+len(overlayFileSuffix):] + contents, err := ioutil.ReadFile(filepath.Join(dir, fragment)) + if err != nil { + t.Fatal(err) + } + overlays[partial] = contents + } + } + + modules := []packagestest.Module{ + { + Name: testModule, + Files: files, + Overlay: overlays, + }, + } + switch mode { + case "Modules": + datum.Exported = packagestest.Export(t, packagestest.Modules, modules) + case "GOPATH": + datum.Exported = packagestest.Export(t, packagestest.GOPATH, modules) + case "MultiModule": + files := map[string]interface{}{} + for k, v := range modules[0].Files { + files[filepath.Join("testmodule", k)] = v + } + modules[0].Files = files + + overlays := map[string][]byte{} + for k, v := range modules[0].Overlay { + overlays[filepath.Join("testmodule", k)] = v + } + modules[0].Overlay = overlays + + golden := map[string]*Golden{} + for k, v := range datum.golden { + if k == summaryFile { + golden[k] = v + } else { + golden[filepath.Join("testmodule", k)] = v + } + } + datum.golden = golden + + datum.Exported = packagestest.Export(t, packagestest.Modules, modules) + default: + panic("unknown mode " + mode) + } + + for _, m := range modules { + for fragment := range m.Files { + filename := datum.Exported.File(m.Name, fragment) + datum.fragments[filename] = fragment + } + } + + // Turn off go/packages debug logging. + datum.Exported.Config.Logf = nil + datum.Config.Logf = nil + + // Merge the exported.Config with the view.Config. + datum.Config = *datum.Exported.Config + datum.Config.Fset = token.NewFileSet() + datum.Config.Context = Context(nil) + datum.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { + panic("ParseFile should not be called") + } + + // Do a first pass to collect special markers for completion and workspace symbols. + if err := datum.Exported.Expect(map[string]interface{}{ + "item": func(name string, r packagestest.Range, _ []string) { + datum.Exported.Mark(name, r) + }, + "symbol": func(name string, r packagestest.Range, _ []string) { + datum.Exported.Mark(name, r) + }, + }); err != nil { + t.Fatal(err) + } + + // Collect any data that needs to be used by subsequent tests. + if err := datum.Exported.Expect(map[string]interface{}{ + "codelens": datum.collectCodeLens, + "diag": datum.collectDiagnostics, + "item": datum.collectCompletionItems, + "complete": datum.collectCompletions(CompletionDefault), + "unimported": datum.collectCompletions(CompletionUnimported), + "deep": datum.collectCompletions(CompletionDeep), + "fuzzy": datum.collectCompletions(CompletionFuzzy), + "casesensitive": datum.collectCompletions(CompletionCaseSensitive), + "rank": datum.collectCompletions(CompletionRank), + "snippet": datum.collectCompletionSnippets, + "fold": datum.collectFoldingRanges, + "format": datum.collectFormats, + "import": datum.collectImports, + "semantic": datum.collectSemanticTokens, + "godef": datum.collectDefinitions, + "implementations": datum.collectImplementations, + "typdef": datum.collectTypeDefinitions, + "hover": datum.collectHoverDefinitions, + "highlight": datum.collectHighlights, + "refs": datum.collectReferences, + "rename": datum.collectRenames, + "prepare": datum.collectPrepareRenames, + "symbol": datum.collectSymbols, + "signature": datum.collectSignatures, + "link": datum.collectLinks, + "suggestedfix": datum.collectSuggestedFixes, + "extractfunc": datum.collectFunctionExtractions, + "incomingcalls": datum.collectIncomingCalls, + "outgoingcalls": datum.collectOutgoingCalls, + }); err != nil { + t.Fatal(err) + } + for _, symbols := range datum.Symbols { + for i := range symbols { + children := datum.symbolsChildren[symbols[i].Name] + symbols[i].Children = children + } + } + // Collect names for the entries that require golden files. + if err := datum.Exported.Expect(map[string]interface{}{ + "godef": datum.collectDefinitionNames, + "hover": datum.collectDefinitionNames, + "workspacesymbol": datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault), + "workspacesymbolfuzzy": datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy), + "workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive), + }); err != nil { + t.Fatal(err) + } + if mode == "MultiModule" { + if err := os.Rename(filepath.Join(datum.Config.Dir, "go.mod"), filepath.Join(datum.Config.Dir, "testmodule/go.mod")); err != nil { + t.Fatal(err) + } + } + + return datum +} + +func Run(t *testing.T, tests Tests, data *Data) { + t.Helper() + checkData(t, data) + + eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) { + t.Helper() + + for src, exp := range cases { + for i, e := range exp { + t.Run(SpanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) { + t.Helper() + if strings.Contains(t.Name(), "complit") || strings.Contains(t.Name(), "UnimportedCompletion") { + if data.mode == "MultiModule" { + t.Skip("Unimported completions are broken in multi-module mode") + } + } + if strings.Contains(t.Name(), "cgo") { + testenv.NeedsTool(t, "cgo") + } + if strings.Contains(t.Name(), "declarecgo") { + testenv.NeedsGo1Point(t, 15) + } + test(t, src, e, data.CompletionItems) + }) + } + + } + } + + eachWorkspaceSymbols := func(t *testing.T, cases map[string][]protocol.SymbolInformation, test func(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})) { + t.Helper() + + for query, expectedSymbols := range cases { + name := query + if name == "" { + name = "EmptyQuery" + } + t.Run(name, func(t *testing.T) { + t.Helper() + dirs := make(map[string]struct{}) + for _, si := range expectedSymbols { + d := filepath.Dir(si.Location.URI.SpanURI().Filename()) + if _, ok := dirs[d]; !ok { + dirs[d] = struct{}{} + } + } + test(t, query, expectedSymbols, dirs) + }) + } + } + + t.Run("CallHierarchy", func(t *testing.T) { + t.Helper() + for spn, callHierarchyResult := range data.CallHierarchy { + t.Run(SpanName(spn), func(t *testing.T) { + t.Helper() + tests.CallHierarchy(t, spn, callHierarchyResult) + }) + } + }) + + t.Run("Completion", func(t *testing.T) { + t.Helper() + eachCompletion(t, data.Completions, tests.Completion) + }) + + t.Run("CompletionSnippets", func(t *testing.T) { + t.Helper() + for _, placeholders := range []bool{true, false} { + for src, expecteds := range data.CompletionSnippets { + for i, expected := range expecteds { + name := SpanName(src) + "_" + strconv.Itoa(i+1) + if placeholders { + name += "_placeholders" + } + + t.Run(name, func(t *testing.T) { + t.Helper() + tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems) + }) + } + } + } + }) + + t.Run("UnimportedCompletion", func(t *testing.T) { + t.Helper() + eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion) + }) + + t.Run("DeepCompletion", func(t *testing.T) { + t.Helper() + eachCompletion(t, data.DeepCompletions, tests.DeepCompletion) + }) + + t.Run("FuzzyCompletion", func(t *testing.T) { + t.Helper() + eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion) + }) + + t.Run("CaseSensitiveCompletion", func(t *testing.T) { + t.Helper() + eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion) + }) + + t.Run("RankCompletions", func(t *testing.T) { + t.Helper() + eachCompletion(t, data.RankCompletions, tests.RankCompletion) + }) + + t.Run("CodeLens", func(t *testing.T) { + t.Helper() + for uri, want := range data.CodeLens { + // Check if we should skip this URI if the -modfile flag is not available. + if shouldSkip(data, uri) { + continue + } + t.Run(uriName(uri), func(t *testing.T) { + t.Helper() + tests.CodeLens(t, uri, want) + }) + } + }) + + t.Run("Diagnostics", func(t *testing.T) { + t.Helper() + for uri, want := range data.Diagnostics { + // Check if we should skip this URI if the -modfile flag is not available. + if shouldSkip(data, uri) { + continue + } + t.Run(uriName(uri), func(t *testing.T) { + t.Helper() + tests.Diagnostics(t, uri, want) + }) + } + }) + + t.Run("FoldingRange", func(t *testing.T) { + t.Helper() + for _, spn := range data.FoldingRanges { + t.Run(uriName(spn.URI()), func(t *testing.T) { + t.Helper() + tests.FoldingRanges(t, spn) + }) + } + }) + + t.Run("Format", func(t *testing.T) { + t.Helper() + for _, spn := range data.Formats { + t.Run(uriName(spn.URI()), func(t *testing.T) { + t.Helper() + tests.Format(t, spn) + }) + } + }) + + t.Run("Import", func(t *testing.T) { + t.Helper() + for _, spn := range data.Imports { + t.Run(uriName(spn.URI()), func(t *testing.T) { + t.Helper() + tests.Import(t, spn) + }) + } + }) + + t.Run("SemanticTokens", func(t *testing.T) { + t.Helper() + for _, spn := range data.SemanticTokens { + t.Run(uriName(spn.URI()), func(t *testing.T) { + t.Helper() + tests.SemanticTokens(t, spn) + }) + } + }) + + t.Run("SuggestedFix", func(t *testing.T) { + t.Helper() + for spn, actionKinds := range data.SuggestedFixes { + // Check if we should skip this spn if the -modfile flag is not available. + if shouldSkip(data, spn.URI()) { + continue + } + t.Run(SpanName(spn), func(t *testing.T) { + t.Helper() + tests.SuggestedFix(t, spn, actionKinds) + }) + } + }) + + t.Run("FunctionExtraction", func(t *testing.T) { + t.Helper() + for start, end := range data.FunctionExtractions { + // Check if we should skip this spn if the -modfile flag is not available. + if shouldSkip(data, start.URI()) { + continue + } + t.Run(SpanName(start), func(t *testing.T) { + t.Helper() + tests.FunctionExtraction(t, start, end) + }) + } + }) + + t.Run("Definition", func(t *testing.T) { + t.Helper() + for spn, d := range data.Definitions { + t.Run(SpanName(spn), func(t *testing.T) { + t.Helper() + if strings.Contains(t.Name(), "cgo") { + testenv.NeedsTool(t, "cgo") + } + if strings.Contains(t.Name(), "declarecgo") { + testenv.NeedsGo1Point(t, 15) + } + tests.Definition(t, spn, d) + }) + } + }) + + t.Run("Implementation", func(t *testing.T) { + t.Helper() + for spn, m := range data.Implementations { + t.Run(SpanName(spn), func(t *testing.T) { + t.Helper() + tests.Implementation(t, spn, m) + }) + } + }) + + t.Run("Highlight", func(t *testing.T) { + t.Helper() + for pos, locations := range data.Highlights { + t.Run(SpanName(pos), func(t *testing.T) { + t.Helper() + tests.Highlight(t, pos, locations) + }) + } + }) + + t.Run("References", func(t *testing.T) { + t.Helper() + for src, itemList := range data.References { + t.Run(SpanName(src), func(t *testing.T) { + t.Helper() + tests.References(t, src, itemList) + }) + } + }) + + t.Run("Renames", func(t *testing.T) { + t.Helper() + for spn, newText := range data.Renames { + t.Run(uriName(spn.URI())+"_"+newText, func(t *testing.T) { + t.Helper() + tests.Rename(t, spn, newText) + }) + } + }) + + t.Run("PrepareRenames", func(t *testing.T) { + t.Helper() + for src, want := range data.PrepareRenames { + t.Run(SpanName(src), func(t *testing.T) { + t.Helper() + tests.PrepareRename(t, src, want) + }) + } + }) + + t.Run("Symbols", func(t *testing.T) { + t.Helper() + for uri, expectedSymbols := range data.Symbols { + t.Run(uriName(uri), func(t *testing.T) { + t.Helper() + tests.Symbols(t, uri, expectedSymbols) + }) + } + }) + + t.Run("WorkspaceSymbols", func(t *testing.T) { + t.Helper() + eachWorkspaceSymbols(t, data.WorkspaceSymbols, tests.WorkspaceSymbols) + }) + + t.Run("FuzzyWorkspaceSymbols", func(t *testing.T) { + t.Helper() + eachWorkspaceSymbols(t, data.FuzzyWorkspaceSymbols, tests.FuzzyWorkspaceSymbols) + }) + + t.Run("CaseSensitiveWorkspaceSymbols", func(t *testing.T) { + t.Helper() + eachWorkspaceSymbols(t, data.CaseSensitiveWorkspaceSymbols, tests.CaseSensitiveWorkspaceSymbols) + }) + + t.Run("SignatureHelp", func(t *testing.T) { + t.Helper() + for spn, expectedSignature := range data.Signatures { + t.Run(SpanName(spn), func(t *testing.T) { + t.Helper() + tests.SignatureHelp(t, spn, expectedSignature) + }) + } + }) + + t.Run("Link", func(t *testing.T) { + t.Helper() + for uri, wantLinks := range data.Links { + // If we are testing GOPATH, then we do not want links with + // the versions attached (pkg.go.dev/repoa/moda@v1.1.0/pkg), + // unless the file is a go.mod, then we can skip it alltogether. + if data.Exported.Exporter == packagestest.GOPATH { + if strings.HasSuffix(uri.Filename(), ".mod") { + continue + } + re := regexp.MustCompile(`@v\d+\.\d+\.[\w-]+`) + for i, link := range wantLinks { + wantLinks[i].Target = re.ReplaceAllString(link.Target, "") + } + } + t.Run(uriName(uri), func(t *testing.T) { + t.Helper() + tests.Link(t, uri, wantLinks) + }) + } + }) + + if *UpdateGolden { + for _, golden := range data.golden { + if !golden.Modified { + continue + } + sort.Slice(golden.Archive.Files, func(i, j int) bool { + return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name + }) + if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil { + t.Fatal(err) + } + } + } +} + +func checkData(t *testing.T, data *Data) { + buf := &bytes.Buffer{} + diagnosticsCount := 0 + for _, want := range data.Diagnostics { + diagnosticsCount += len(want) + } + linksCount := 0 + for _, want := range data.Links { + linksCount += len(want) + } + definitionCount := 0 + typeDefinitionCount := 0 + for _, d := range data.Definitions { + if d.IsType { + typeDefinitionCount++ + } else { + definitionCount++ + } + } + + snippetCount := 0 + for _, want := range data.CompletionSnippets { + snippetCount += len(want) + } + + countCompletions := func(c map[span.Span][]Completion) (count int) { + for _, want := range c { + count += len(want) + } + return count + } + + countCodeLens := func(c map[span.URI][]protocol.CodeLens) (count int) { + for _, want := range c { + count += len(want) + } + return count + } + + fmt.Fprintf(buf, "CallHierarchyCount = %v\n", len(data.CallHierarchy)) + fmt.Fprintf(buf, "CodeLensCount = %v\n", countCodeLens(data.CodeLens)) + fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions)) + fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount) + fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions)) + fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", countCompletions(data.DeepCompletions)) + fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", countCompletions(data.FuzzyCompletions)) + fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", countCompletions(data.RankCompletions)) + fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", countCompletions(data.CaseSensitiveCompletions)) + fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount) + fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges)) + fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats)) + fmt.Fprintf(buf, "ImportCount = %v\n", len(data.Imports)) + fmt.Fprintf(buf, "SemanticTokenCount = %v\n", len(data.SemanticTokens)) + fmt.Fprintf(buf, "SuggestedFixCount = %v\n", len(data.SuggestedFixes)) + fmt.Fprintf(buf, "FunctionExtractionCount = %v\n", len(data.FunctionExtractions)) + fmt.Fprintf(buf, "DefinitionsCount = %v\n", definitionCount) + fmt.Fprintf(buf, "TypeDefinitionsCount = %v\n", typeDefinitionCount) + fmt.Fprintf(buf, "HighlightsCount = %v\n", len(data.Highlights)) + fmt.Fprintf(buf, "ReferencesCount = %v\n", len(data.References)) + fmt.Fprintf(buf, "RenamesCount = %v\n", len(data.Renames)) + fmt.Fprintf(buf, "PrepareRenamesCount = %v\n", len(data.PrepareRenames)) + fmt.Fprintf(buf, "SymbolsCount = %v\n", len(data.Symbols)) + fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", len(data.WorkspaceSymbols)) + fmt.Fprintf(buf, "FuzzyWorkspaceSymbolsCount = %v\n", len(data.FuzzyWorkspaceSymbols)) + fmt.Fprintf(buf, "CaseSensitiveWorkspaceSymbolsCount = %v\n", len(data.CaseSensitiveWorkspaceSymbols)) + fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures)) + fmt.Fprintf(buf, "LinksCount = %v\n", linksCount) + fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations)) + + want := string(data.Golden("summary", summaryFile, func() ([]byte, error) { + return buf.Bytes(), nil + })) + got := buf.String() + if want != got { + t.Errorf("test summary does not match:\n%s", Diff(want, got)) + } +} + +func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) { + data.mappersMu.Lock() + defer data.mappersMu.Unlock() + + if _, ok := data.mappers[uri]; !ok { + content, err := data.Exported.FileContents(uri.Filename()) + if err != nil { + return nil, err + } + converter := span.NewContentConverter(uri.Filename(), content) + data.mappers[uri] = &protocol.ColumnMapper{ + URI: uri, + Converter: converter, + Content: content, + } + } + return data.mappers[uri], nil +} + +func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte { + data.t.Helper() + fragment, found := data.fragments[target] + if !found { + if filepath.IsAbs(target) { + data.t.Fatalf("invalid golden file fragment %v", target) + } + fragment = target + } + golden := data.golden[fragment] + if golden == nil { + if !*UpdateGolden { + data.t.Fatalf("could not find golden file %v: %v", fragment, tag) + } + var subdir string + if fragment != summaryFile { + subdir = "primarymod" + } + golden = &Golden{ + Filename: filepath.Join(data.dir, subdir, fragment+goldenFileSuffix), + Archive: &txtar.Archive{}, + Modified: true, + } + data.golden[fragment] = golden + } + var file *txtar.File + for i := range golden.Archive.Files { + f := &golden.Archive.Files[i] + if f.Name == tag { + file = f + break + } + } + if *UpdateGolden { + if file == nil { + golden.Archive.Files = append(golden.Archive.Files, txtar.File{ + Name: tag, + }) + file = &golden.Archive.Files[len(golden.Archive.Files)-1] + } + contents, err := update() + if err != nil { + data.t.Fatalf("could not update golden file %v: %v", fragment, err) + } + file.Data = append(contents, '\n') // add trailing \n for txtar + golden.Modified = true + + } + if file == nil { + data.t.Fatalf("could not find golden contents %v: %v", fragment, tag) + } + if len(file.Data) == 0 { + return file.Data + } + return file.Data[:len(file.Data)-1] // drop the trailing \n +} + +func (data *Data) collectCodeLens(spn span.Span, title, cmd string) { + if _, ok := data.CodeLens[spn.URI()]; !ok { + data.CodeLens[spn.URI()] = []protocol.CodeLens{} + } + m, err := data.Mapper(spn.URI()) + if err != nil { + return + } + rng, err := m.Range(spn) + if err != nil { + return + } + data.CodeLens[spn.URI()] = append(data.CodeLens[spn.URI()], protocol.CodeLens{ + Range: rng, + Command: protocol.Command{ + Title: title, + Command: cmd, + }, + }) +} + +func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg, msgSeverity string) { + if _, ok := data.Diagnostics[spn.URI()]; !ok { + data.Diagnostics[spn.URI()] = []*source.Diagnostic{} + } + m, err := data.Mapper(spn.URI()) + if err != nil { + return + } + rng, err := m.Range(spn) + if err != nil { + return + } + severity := protocol.SeverityError + switch msgSeverity { + case "error": + severity = protocol.SeverityError + case "warning": + severity = protocol.SeverityWarning + case "hint": + severity = protocol.SeverityHint + case "information": + severity = protocol.SeverityInformation + } + // This is not the correct way to do this, but it seems excessive to do the full conversion here. + want := &source.Diagnostic{ + Range: rng, + Severity: severity, + Source: msgSource, + Message: msg, + } + data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want) +} + +func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) { + result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) { + m[src] = append(m[src], Completion{ + CompletionItems: expected, + }) + } + switch typ { + case CompletionDeep: + return func(src span.Span, expected []token.Pos) { + result(data.DeepCompletions, src, expected) + } + case CompletionUnimported: + return func(src span.Span, expected []token.Pos) { + result(data.UnimportedCompletions, src, expected) + } + case CompletionFuzzy: + return func(src span.Span, expected []token.Pos) { + result(data.FuzzyCompletions, src, expected) + } + case CompletionRank: + return func(src span.Span, expected []token.Pos) { + result(data.RankCompletions, src, expected) + } + case CompletionCaseSensitive: + return func(src span.Span, expected []token.Pos) { + result(data.CaseSensitiveCompletions, src, expected) + } + default: + return func(src span.Span, expected []token.Pos) { + result(data.Completions, src, expected) + } + } +} + +func (data *Data) collectCompletionItems(pos token.Pos, args []string) { + if len(args) < 3 { + loc := data.Exported.ExpectFileSet.Position(pos) + data.t.Fatalf("%s:%d: @item expects at least 3 args, got %d", + loc.Filename, loc.Line, len(args)) + } + label, detail, kind := args[0], args[1], args[2] + var documentation string + if len(args) == 4 { + documentation = args[3] + } + data.CompletionItems[pos] = &completion.CompletionItem{ + Label: label, + Detail: detail, + Kind: protocol.ParseCompletionItemKind(kind), + Documentation: documentation, + } +} + +func (data *Data) collectFoldingRanges(spn span.Span) { + data.FoldingRanges = append(data.FoldingRanges, spn) +} + +func (data *Data) collectFormats(spn span.Span) { + data.Formats = append(data.Formats, spn) +} + +func (data *Data) collectImports(spn span.Span) { + data.Imports = append(data.Imports, spn) +} + +func (data *Data) collectSemanticTokens(spn span.Span) { + data.SemanticTokens = append(data.SemanticTokens, spn) +} + +func (data *Data) collectSuggestedFixes(spn span.Span, actionKind string) { + if _, ok := data.SuggestedFixes[spn]; !ok { + data.SuggestedFixes[spn] = []string{} + } + data.SuggestedFixes[spn] = append(data.SuggestedFixes[spn], actionKind) +} + +func (data *Data) collectFunctionExtractions(start span.Span, end span.Span) { + if _, ok := data.FunctionExtractions[start]; !ok { + data.FunctionExtractions[start] = end + } +} + +func (data *Data) collectDefinitions(src, target span.Span) { + data.Definitions[src] = Definition{ + Src: src, + Def: target, + } +} + +func (data *Data) collectImplementations(src span.Span, targets []span.Span) { + data.Implementations[src] = targets +} + +func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) { + for _, call := range calls { + m, err := data.Mapper(call.URI()) + if err != nil { + data.t.Fatal(err) + } + rng, err := m.Range(call) + if err != nil { + data.t.Fatal(err) + } + // we're only comparing protocol.range + if data.CallHierarchy[src] != nil { + data.CallHierarchy[src].IncomingCalls = append(data.CallHierarchy[src].IncomingCalls, + protocol.CallHierarchyItem{ + URI: protocol.DocumentURI(call.URI()), + Range: rng, + }) + } else { + data.CallHierarchy[src] = &CallHierarchyResult{ + IncomingCalls: []protocol.CallHierarchyItem{ + {URI: protocol.DocumentURI(call.URI()), Range: rng}, + }, + } + } + } +} + +func (data *Data) collectOutgoingCalls(src span.Span, calls []span.Span) { + for _, call := range calls { + m, err := data.Mapper(call.URI()) + if err != nil { + data.t.Fatal(err) + } + rng, err := m.Range(call) + if err != nil { + data.t.Fatal(err) + } + // we're only comparing protocol.range + if data.CallHierarchy[src] != nil { + data.CallHierarchy[src].OutgoingCalls = append(data.CallHierarchy[src].OutgoingCalls, + protocol.CallHierarchyItem{ + URI: protocol.DocumentURI(call.URI()), + Range: rng, + }) + } else { + data.CallHierarchy[src] = &CallHierarchyResult{ + OutgoingCalls: []protocol.CallHierarchyItem{ + {URI: protocol.DocumentURI(call.URI()), Range: rng}, + }, + } + } + } +} + +func (data *Data) collectHoverDefinitions(src, target span.Span) { + data.Definitions[src] = Definition{ + Src: src, + Def: target, + OnlyHover: true, + } +} + +func (data *Data) collectTypeDefinitions(src, target span.Span) { + data.Definitions[src] = Definition{ + Src: src, + Def: target, + IsType: true, + } +} + +func (data *Data) collectDefinitionNames(src span.Span, name string) { + d := data.Definitions[src] + d.Name = name + data.Definitions[src] = d +} + +func (data *Data) collectHighlights(src span.Span, expected []span.Span) { + // Declaring a highlight in a test file: @highlight(src, expected1, expected2) + data.Highlights[src] = append(data.Highlights[src], expected...) +} + +func (data *Data) collectReferences(src span.Span, expected []span.Span) { + data.References[src] = expected +} + +func (data *Data) collectRenames(src span.Span, newText string) { + data.Renames[src] = newText +} + +func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placeholder string) { + m, err := data.Mapper(src.URI()) + if err != nil { + data.t.Fatal(err) + } + // Convert range to span and then to protocol.Range. + spn, err := rng.Span() + if err != nil { + data.t.Fatal(err) + } + prng, err := m.Range(spn) + if err != nil { + data.t.Fatal(err) + } + data.PrepareRenames[src] = &source.PrepareItem{ + Range: prng, + Text: placeholder, + } +} + +// collectSymbols is responsible for collecting @symbol annotations. +func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string, siName string) { + m, err := data.Mapper(spn.URI()) + if err != nil { + data.t.Fatal(err) + } + rng, err := m.Range(spn) + if err != nil { + data.t.Fatal(err) + } + sym := protocol.DocumentSymbol{ + Name: name, + Kind: protocol.ParseSymbolKind(kind), + SelectionRange: rng, + } + if parentName == "" { + data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym) + } else { + data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym) + } + + // Reuse @symbol in the workspace symbols tests. + si := protocol.SymbolInformation{ + Name: siName, + Kind: sym.Kind, + Location: protocol.Location{ + URI: protocol.URIFromSpanURI(spn.URI()), + Range: sym.SelectionRange, + }, + } + data.symbolInformation[spn] = si +} + +func (data *Data) collectWorkspaceSymbols(typ WorkspaceSymbolsTestType) func(string, []span.Span) { + switch typ { + case WorkspaceSymbolsFuzzy: + return func(query string, targets []span.Span) { + data.FuzzyWorkspaceSymbols[query] = make([]protocol.SymbolInformation, 0, len(targets)) + for _, target := range targets { + data.FuzzyWorkspaceSymbols[query] = append(data.FuzzyWorkspaceSymbols[query], data.symbolInformation[target]) + } + } + case WorkspaceSymbolsCaseSensitive: + return func(query string, targets []span.Span) { + data.CaseSensitiveWorkspaceSymbols[query] = make([]protocol.SymbolInformation, 0, len(targets)) + for _, target := range targets { + data.CaseSensitiveWorkspaceSymbols[query] = append(data.CaseSensitiveWorkspaceSymbols[query], data.symbolInformation[target]) + } + } + default: + return func(query string, targets []span.Span) { + data.WorkspaceSymbols[query] = make([]protocol.SymbolInformation, 0, len(targets)) + for _, target := range targets { + data.WorkspaceSymbols[query] = append(data.WorkspaceSymbols[query], data.symbolInformation[target]) + } + } + } +} + +func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) { + data.Signatures[spn] = &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: signature, + }, + }, + ActiveParameter: float64(activeParam), + } + // Hardcode special case to test the lack of a signature. + if signature == "" && activeParam == 0 { + data.Signatures[spn] = nil + } +} + +func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) { + data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{ + CompletionItem: item, + PlainSnippet: plain, + PlaceholderSnippet: placeholder, + }) +} + +func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) { + position := fset.Position(note.Pos) + uri := spn.URI() + data.Links[uri] = append(data.Links[uri], Link{ + Src: spn, + Target: link, + NotePosition: position, + }) +} + +func uriName(uri span.URI) string { + return filepath.Base(strings.TrimSuffix(uri.Filename(), ".go")) +} + +func SpanName(spn span.Span) string { + return fmt.Sprintf("%v_%v_%v", uriName(spn.URI()), spn.Start().Line(), spn.Start().Column()) +} + +func CopyFolderToTempDir(folder string) (string, error) { + if _, err := os.Stat(folder); err != nil { + return "", err + } + dst, err := ioutil.TempDir("", "modfile_test") + if err != nil { + return "", err + } + fds, err := ioutil.ReadDir(folder) + if err != nil { + return "", err + } + for _, fd := range fds { + srcfp := filepath.Join(folder, fd.Name()) + stat, err := os.Stat(srcfp) + if err != nil { + return "", err + } + if !stat.Mode().IsRegular() { + return "", fmt.Errorf("cannot copy non regular file %s", srcfp) + } + contents, err := ioutil.ReadFile(srcfp) + if err != nil { + return "", err + } + if err := ioutil.WriteFile(filepath.Join(dst, fd.Name()), contents, stat.Mode()); err != nil { + return "", err + } + } + return dst, nil +} + +func shouldSkip(data *Data, uri span.URI) bool { + if data.ModfileFlagAvailable { + return false + } + // If the -modfile flag is not available, then we do not want to run + // any tests on the go.mod file. + if strings.HasSuffix(uri.Filename(), ".mod") { + return true + } + // If the -modfile flag is not available, then we do not want to test any + // uri that contains "go mod tidy". + m, err := data.Mapper(uri) + return err == nil && strings.Contains(string(m.Content), ", \"go mod tidy\",") +}