+++ /dev/null
-// 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\",")
-}