2019-06-12 07:55:08 -06:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
2019-04-16 13:47:48 -06:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package tests
|
|
|
|
|
|
|
|
import (
|
2019-08-14 18:12:18 -06:00
|
|
|
"bytes"
|
2019-04-16 13:47:48 -06:00
|
|
|
"context"
|
2019-04-09 20:23:02 -06:00
|
|
|
"flag"
|
2019-08-14 18:12:18 -06:00
|
|
|
"fmt"
|
2019-04-16 13:47:48 -06:00
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
|
|
|
"io/ioutil"
|
|
|
|
"path/filepath"
|
2019-05-01 17:16:03 -06:00
|
|
|
"sort"
|
2019-04-16 13:47:48 -06:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
2019-07-07 16:25:19 -06:00
|
|
|
"golang.org/x/tools/go/expect"
|
2019-04-16 13:47:48 -06:00
|
|
|
"golang.org/x/tools/go/packages"
|
|
|
|
"golang.org/x/tools/go/packages/packagestest"
|
2019-08-14 18:12:18 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-04-16 13:47:48 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
|
|
"golang.org/x/tools/internal/span"
|
2019-05-01 17:16:03 -06:00
|
|
|
"golang.org/x/tools/internal/txtar"
|
2019-04-16 13:47:48 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// We hardcode the expected number of test cases to ensure that all tests
|
|
|
|
// are being executed. If a test is added, this number must be changed.
|
|
|
|
const (
|
2019-09-09 10:25:22 -06:00
|
|
|
ExpectedCompletionsCount = 160
|
2019-09-06 15:22:54 -06:00
|
|
|
ExpectedCompletionSnippetCount = 16
|
2019-08-02 17:45:56 -06:00
|
|
|
ExpectedDiagnosticsCount = 21
|
2019-07-26 16:17:04 -06:00
|
|
|
ExpectedFormatCount = 6
|
2019-05-31 20:42:59 -06:00
|
|
|
ExpectedImportCount = 2
|
2019-09-04 11:16:09 -06:00
|
|
|
ExpectedSuggestedFixCount = 1
|
2019-08-21 17:36:09 -06:00
|
|
|
ExpectedDefinitionsCount = 39
|
2019-04-28 21:19:54 -06:00
|
|
|
ExpectedTypeDefinitionsCount = 2
|
2019-08-30 12:11:42 -06:00
|
|
|
ExpectedFoldingRangesCount = 2
|
2019-04-28 21:19:54 -06:00
|
|
|
ExpectedHighlightsCount = 2
|
2019-09-04 15:23:20 -06:00
|
|
|
ExpectedReferencesCount = 6
|
2019-08-21 20:40:36 -06:00
|
|
|
ExpectedRenamesCount = 20
|
2019-08-22 11:31:03 -06:00
|
|
|
ExpectedPrepareRenamesCount = 8
|
2019-04-28 21:19:54 -06:00
|
|
|
ExpectedSymbolsCount = 1
|
2019-06-28 19:27:41 -06:00
|
|
|
ExpectedSignaturesCount = 21
|
2019-07-07 16:25:19 -06:00
|
|
|
ExpectedLinksCount = 4
|
2019-04-16 13:47:48 -06:00
|
|
|
)
|
|
|
|
|
2019-04-09 20:23:02 -06:00
|
|
|
const (
|
2019-05-01 17:16:03 -06:00
|
|
|
overlayFileSuffix = ".overlay"
|
|
|
|
goldenFileSuffix = ".golden"
|
|
|
|
inFileSuffix = ".in"
|
|
|
|
testModule = "golang.org/x/tools/internal/lsp"
|
2019-04-09 20:23:02 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
var updateGolden = flag.Bool("golden", false, "Update golden files")
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
type Diagnostics map[span.URI][]source.Diagnostic
|
|
|
|
type CompletionItems map[token.Pos]*source.CompletionItem
|
2019-06-17 21:56:06 -06:00
|
|
|
type Completions map[span.Span]Completion
|
2019-04-28 21:19:54 -06:00
|
|
|
type CompletionSnippets map[span.Span]CompletionSnippet
|
2019-08-28 19:48:29 -06:00
|
|
|
type FoldingRanges []span.Span
|
2019-04-22 16:15:39 -06:00
|
|
|
type Formats []span.Span
|
2019-05-31 20:42:59 -06:00
|
|
|
type Imports []span.Span
|
2019-09-04 11:16:09 -06:00
|
|
|
type SuggestedFixes []span.Span
|
2019-04-16 13:47:48 -06:00
|
|
|
type Definitions map[span.Span]Definition
|
|
|
|
type Highlights map[string][]span.Span
|
2019-06-07 08:04:22 -06:00
|
|
|
type References map[span.Span][]span.Span
|
2019-06-18 08:23:37 -06:00
|
|
|
type Renames map[span.Span]string
|
2019-08-22 11:31:03 -06:00
|
|
|
type PrepareRenames map[span.Span]*source.PrepareItem
|
2019-09-05 16:54:05 -06:00
|
|
|
type Symbols map[span.URI][]protocol.DocumentSymbol
|
|
|
|
type SymbolsChildren map[string][]protocol.DocumentSymbol
|
2019-06-28 19:27:41 -06:00
|
|
|
type Signatures map[span.Span]*source.SignatureInformation
|
2019-04-24 09:33:45 -06:00
|
|
|
type Links map[span.URI][]Link
|
2019-04-16 13:47:48 -06:00
|
|
|
|
|
|
|
type Data struct {
|
2019-04-28 21:19:54 -06:00
|
|
|
Config packages.Config
|
|
|
|
Exported *packagestest.Exported
|
|
|
|
Diagnostics Diagnostics
|
|
|
|
CompletionItems CompletionItems
|
|
|
|
Completions Completions
|
|
|
|
CompletionSnippets CompletionSnippets
|
2019-08-28 19:48:29 -06:00
|
|
|
FoldingRanges FoldingRanges
|
2019-04-28 21:19:54 -06:00
|
|
|
Formats Formats
|
2019-05-31 20:42:59 -06:00
|
|
|
Imports Imports
|
2019-09-04 11:16:09 -06:00
|
|
|
SuggestedFixes SuggestedFixes
|
2019-04-28 21:19:54 -06:00
|
|
|
Definitions Definitions
|
|
|
|
Highlights Highlights
|
2019-06-07 08:04:22 -06:00
|
|
|
References References
|
2019-06-18 08:23:37 -06:00
|
|
|
Renames Renames
|
2019-08-22 11:31:03 -06:00
|
|
|
PrepareRenames PrepareRenames
|
2019-04-28 21:19:54 -06:00
|
|
|
Symbols Symbols
|
|
|
|
symbolsChildren SymbolsChildren
|
|
|
|
Signatures Signatures
|
2019-04-24 09:33:45 -06:00
|
|
|
Links Links
|
2019-04-09 20:23:02 -06:00
|
|
|
|
|
|
|
t testing.TB
|
|
|
|
fragments map[string]string
|
|
|
|
dir string
|
2019-05-01 17:16:03 -06:00
|
|
|
golden map[string]*Golden
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type Tests interface {
|
|
|
|
Diagnostics(*testing.T, Diagnostics)
|
2019-04-28 21:19:54 -06:00
|
|
|
Completion(*testing.T, Completions, CompletionSnippets, CompletionItems)
|
2019-08-28 19:48:29 -06:00
|
|
|
FoldingRange(*testing.T, FoldingRanges)
|
2019-04-16 13:47:48 -06:00
|
|
|
Format(*testing.T, Formats)
|
2019-05-31 20:42:59 -06:00
|
|
|
Import(*testing.T, Imports)
|
2019-09-04 11:16:09 -06:00
|
|
|
SuggestedFix(*testing.T, SuggestedFixes)
|
2019-04-16 13:47:48 -06:00
|
|
|
Definition(*testing.T, Definitions)
|
|
|
|
Highlight(*testing.T, Highlights)
|
2019-06-07 08:04:22 -06:00
|
|
|
Reference(*testing.T, References)
|
2019-06-18 08:23:37 -06:00
|
|
|
Rename(*testing.T, Renames)
|
2019-08-22 11:31:03 -06:00
|
|
|
PrepareRename(*testing.T, PrepareRenames)
|
2019-04-16 13:47:48 -06:00
|
|
|
Symbol(*testing.T, Symbols)
|
2019-05-14 19:20:41 -06:00
|
|
|
SignatureHelp(*testing.T, Signatures)
|
2019-04-24 09:33:45 -06:00
|
|
|
Link(*testing.T, Links)
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type Definition struct {
|
2019-05-15 15:58:16 -06:00
|
|
|
Name string
|
|
|
|
IsType bool
|
|
|
|
OnlyHover bool
|
2019-08-26 22:26:45 -06:00
|
|
|
Src, Def span.Span
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
2019-06-17 21:56:06 -06:00
|
|
|
type CompletionTestType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Full means candidates in test must match full list of candidates.
|
|
|
|
CompletionFull CompletionTestType = iota
|
|
|
|
|
|
|
|
// Partial means candidates in test must be valid and in the right relative order.
|
|
|
|
CompletionPartial
|
|
|
|
)
|
|
|
|
|
|
|
|
type Completion struct {
|
|
|
|
CompletionItems []token.Pos
|
|
|
|
Type CompletionTestType
|
|
|
|
}
|
|
|
|
|
2019-04-28 21:19:54 -06:00
|
|
|
type CompletionSnippet struct {
|
|
|
|
CompletionItem token.Pos
|
|
|
|
PlainSnippet string
|
|
|
|
PlaceholderSnippet string
|
|
|
|
}
|
|
|
|
|
2019-04-24 09:33:45 -06:00
|
|
|
type Link struct {
|
2019-07-07 16:25:19 -06:00
|
|
|
Src span.Span
|
|
|
|
Target string
|
|
|
|
NotePosition token.Position
|
2019-04-24 09:33:45 -06:00
|
|
|
}
|
|
|
|
|
2019-05-01 17:16:03 -06:00
|
|
|
type Golden struct {
|
|
|
|
Filename string
|
|
|
|
Archive *txtar.Archive
|
|
|
|
Modified bool
|
|
|
|
}
|
|
|
|
|
2019-07-10 19:11:23 -06:00
|
|
|
func Context(t testing.TB) context.Context {
|
|
|
|
return context.Background()
|
|
|
|
}
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
data := &Data{
|
2019-04-28 21:19:54 -06:00
|
|
|
Diagnostics: make(Diagnostics),
|
|
|
|
CompletionItems: make(CompletionItems),
|
|
|
|
Completions: make(Completions),
|
|
|
|
CompletionSnippets: make(CompletionSnippets),
|
|
|
|
Definitions: make(Definitions),
|
|
|
|
Highlights: make(Highlights),
|
2019-06-07 08:04:22 -06:00
|
|
|
References: make(References),
|
2019-06-18 08:23:37 -06:00
|
|
|
Renames: make(Renames),
|
2019-08-22 11:31:03 -06:00
|
|
|
PrepareRenames: make(PrepareRenames),
|
2019-04-28 21:19:54 -06:00
|
|
|
Symbols: make(Symbols),
|
|
|
|
symbolsChildren: make(SymbolsChildren),
|
|
|
|
Signatures: make(Signatures),
|
2019-04-24 09:33:45 -06:00
|
|
|
Links: make(Links),
|
2019-04-09 20:23:02 -06:00
|
|
|
|
|
|
|
t: t,
|
|
|
|
dir: dir,
|
|
|
|
fragments: map[string]string{},
|
2019-05-01 17:16:03 -06:00
|
|
|
golden: map[string]*Golden{},
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
files := packagestest.MustCopyFileTree(dir)
|
|
|
|
overlays := map[string][]byte{}
|
|
|
|
for fragment, operation := range files {
|
2019-05-01 17:16:03 -06:00
|
|
|
if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {
|
2019-04-09 20:23:02 -06:00
|
|
|
delete(files, fragment)
|
2019-05-01 17:16:03 -06:00
|
|
|
goldFile := filepath.Join(dir, fragment)
|
|
|
|
archive, err := txtar.ParseFile(goldFile)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("could not read golden file %v: %v", fragment, err)
|
|
|
|
}
|
|
|
|
data.golden[trimmed] = &Golden{
|
|
|
|
Filename: goldFile,
|
|
|
|
Archive: archive,
|
|
|
|
}
|
|
|
|
} else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment {
|
2019-04-16 13:47:48 -06:00
|
|
|
delete(files, fragment)
|
|
|
|
files[trimmed] = operation
|
2019-05-01 17:16:03 -06:00
|
|
|
} else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 {
|
2019-04-16 13:47:48 -06:00
|
|
|
delete(files, fragment)
|
2019-05-01 17:16:03 -06:00
|
|
|
partial := fragment[:index] + fragment[index+len(overlayFileSuffix):]
|
2019-04-16 13:47:48 -06:00
|
|
|
contents, err := ioutil.ReadFile(filepath.Join(dir, fragment))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
overlays[partial] = contents
|
|
|
|
}
|
|
|
|
}
|
|
|
|
modules := []packagestest.Module{
|
|
|
|
{
|
2019-04-09 20:23:02 -06:00
|
|
|
Name: testModule,
|
2019-04-16 13:47:48 -06:00
|
|
|
Files: files,
|
|
|
|
Overlay: overlays,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
data.Exported = packagestest.Export(t, exporter, modules)
|
2019-04-09 20:23:02 -06:00
|
|
|
for fragment, _ := range files {
|
|
|
|
filename := data.Exported.File(testModule, fragment)
|
|
|
|
data.fragments[filename] = fragment
|
|
|
|
}
|
2019-08-13 11:35:13 -06:00
|
|
|
data.Exported.Config.Logf = nil
|
2019-04-16 13:47:48 -06:00
|
|
|
|
|
|
|
// Merge the exported.Config with the view.Config.
|
|
|
|
data.Config = *data.Exported.Config
|
|
|
|
data.Config.Fset = token.NewFileSet()
|
2019-08-13 11:35:13 -06:00
|
|
|
data.Config.Logf = nil
|
2019-07-10 19:11:23 -06:00
|
|
|
data.Config.Context = Context(nil)
|
2019-04-16 13:47:48 -06:00
|
|
|
data.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
|
2019-06-04 20:14:37 -06:00
|
|
|
panic("ParseFile should not be called")
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do a first pass to collect special markers for completion.
|
|
|
|
if err := data.Exported.Expect(map[string]interface{}{
|
2019-08-06 16:51:17 -06:00
|
|
|
"item": func(name string, r packagestest.Range, _ []string) {
|
2019-04-16 13:47:48 -06:00
|
|
|
data.Exported.Mark(name, r)
|
|
|
|
},
|
|
|
|
}); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collect any data that needs to be used by subsequent tests.
|
|
|
|
if err := data.Exported.Expect(map[string]interface{}{
|
2019-06-17 21:56:06 -06:00
|
|
|
"diag": data.collectDiagnostics,
|
|
|
|
"item": data.collectCompletionItems,
|
|
|
|
"complete": data.collectCompletions(CompletionFull),
|
|
|
|
"completePartial": data.collectCompletions(CompletionPartial),
|
|
|
|
"fold": data.collectFoldingRanges,
|
|
|
|
"format": data.collectFormats,
|
|
|
|
"import": data.collectImports,
|
|
|
|
"godef": data.collectDefinitions,
|
|
|
|
"typdef": data.collectTypeDefinitions,
|
|
|
|
"hover": data.collectHoverDefinitions,
|
|
|
|
"highlight": data.collectHighlights,
|
|
|
|
"refs": data.collectReferences,
|
|
|
|
"rename": data.collectRenames,
|
|
|
|
"prepare": data.collectPrepareRenames,
|
|
|
|
"symbol": data.collectSymbols,
|
|
|
|
"signature": data.collectSignatures,
|
|
|
|
"snippet": data.collectCompletionSnippets,
|
|
|
|
"link": data.collectLinks,
|
|
|
|
"suggestedfix": data.collectSuggestedFixes,
|
2019-04-16 13:47:48 -06:00
|
|
|
}); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
for _, symbols := range data.Symbols {
|
|
|
|
for i := range symbols {
|
|
|
|
children := data.symbolsChildren[symbols[i].Name]
|
|
|
|
symbols[i].Children = children
|
|
|
|
}
|
|
|
|
}
|
2019-05-15 15:58:16 -06:00
|
|
|
// Collect names for the entries that require golden files.
|
2019-05-01 17:16:03 -06:00
|
|
|
if err := data.Exported.Expect(map[string]interface{}{
|
|
|
|
"godef": data.collectDefinitionNames,
|
2019-05-15 15:58:16 -06:00
|
|
|
"hover": data.collectDefinitionNames,
|
2019-05-01 17:16:03 -06:00
|
|
|
}); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-04-16 13:47:48 -06:00
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
|
|
|
func Run(t *testing.T, tests Tests, data *Data) {
|
|
|
|
t.Helper()
|
|
|
|
t.Run("Completion", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.Completions) != ExpectedCompletionsCount {
|
|
|
|
t.Errorf("got %v completions expected %v", len(data.Completions), ExpectedCompletionsCount)
|
|
|
|
}
|
2019-04-28 21:19:54 -06:00
|
|
|
if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
|
|
|
|
t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
|
|
|
|
}
|
|
|
|
tests.Completion(t, data.Completions, data.CompletionSnippets, data.CompletionItems)
|
2019-04-16 13:47:48 -06:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Diagnostics", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
diagnosticsCount := 0
|
|
|
|
for _, want := range data.Diagnostics {
|
|
|
|
diagnosticsCount += len(want)
|
|
|
|
}
|
|
|
|
if diagnosticsCount != ExpectedDiagnosticsCount {
|
|
|
|
t.Errorf("got %v diagnostics expected %v", diagnosticsCount, ExpectedDiagnosticsCount)
|
|
|
|
}
|
|
|
|
tests.Diagnostics(t, data.Diagnostics)
|
|
|
|
})
|
|
|
|
|
2019-08-28 19:48:29 -06:00
|
|
|
t.Run("FoldingRange", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.FoldingRanges) != ExpectedFoldingRangesCount {
|
|
|
|
t.Errorf("got %v folding ranges expected %v", len(data.FoldingRanges), ExpectedFoldingRangesCount)
|
|
|
|
}
|
|
|
|
tests.FoldingRange(t, data.FoldingRanges)
|
|
|
|
})
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
t.Run("Format", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.Formats) != ExpectedFormatCount {
|
|
|
|
t.Errorf("got %v formats expected %v", len(data.Formats), ExpectedFormatCount)
|
|
|
|
}
|
|
|
|
tests.Format(t, data.Formats)
|
|
|
|
})
|
|
|
|
|
2019-05-31 20:42:59 -06:00
|
|
|
t.Run("Import", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.Imports) != ExpectedImportCount {
|
|
|
|
t.Errorf("got %v imports expected %v", len(data.Imports), ExpectedImportCount)
|
|
|
|
}
|
|
|
|
tests.Import(t, data.Imports)
|
|
|
|
})
|
|
|
|
|
2019-09-04 11:16:09 -06:00
|
|
|
t.Run("SuggestedFix", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.SuggestedFixes) != ExpectedSuggestedFixCount {
|
|
|
|
t.Errorf("got %v suggested fixes expected %v", len(data.SuggestedFixes), ExpectedSuggestedFixCount)
|
|
|
|
}
|
|
|
|
tests.SuggestedFix(t, data.SuggestedFixes)
|
|
|
|
})
|
|
|
|
|
2019-04-27 20:45:06 -06:00
|
|
|
t.Run("Definition", func(t *testing.T) {
|
2019-04-16 13:47:48 -06:00
|
|
|
t.Helper()
|
|
|
|
if len(data.Definitions) != ExpectedDefinitionsCount {
|
|
|
|
t.Errorf("got %v definitions expected %v", len(data.Definitions), ExpectedDefinitionsCount)
|
|
|
|
}
|
|
|
|
tests.Definition(t, data.Definitions)
|
|
|
|
})
|
|
|
|
|
2019-04-27 20:45:06 -06:00
|
|
|
t.Run("Highlight", func(t *testing.T) {
|
2019-04-16 13:47:48 -06:00
|
|
|
t.Helper()
|
|
|
|
if len(data.Highlights) != ExpectedHighlightsCount {
|
|
|
|
t.Errorf("got %v highlights expected %v", len(data.Highlights), ExpectedHighlightsCount)
|
|
|
|
}
|
|
|
|
tests.Highlight(t, data.Highlights)
|
|
|
|
})
|
|
|
|
|
2019-06-07 08:04:22 -06:00
|
|
|
t.Run("References", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.References) != ExpectedReferencesCount {
|
|
|
|
t.Errorf("got %v references expected %v", len(data.References), ExpectedReferencesCount)
|
|
|
|
}
|
|
|
|
tests.Reference(t, data.References)
|
|
|
|
})
|
|
|
|
|
2019-06-18 08:23:37 -06:00
|
|
|
t.Run("Renames", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.Renames) != ExpectedRenamesCount {
|
|
|
|
t.Errorf("got %v renames expected %v", len(data.Renames), ExpectedRenamesCount)
|
|
|
|
}
|
|
|
|
tests.Rename(t, data.Renames)
|
|
|
|
})
|
|
|
|
|
2019-08-22 11:31:03 -06:00
|
|
|
t.Run("PrepareRenames", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.PrepareRenames) != ExpectedPrepareRenamesCount {
|
|
|
|
t.Errorf("got %v prepare renames expected %v", len(data.PrepareRenames), ExpectedPrepareRenamesCount)
|
|
|
|
}
|
|
|
|
|
|
|
|
tests.PrepareRename(t, data.PrepareRenames)
|
|
|
|
})
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
t.Run("Symbols", func(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
if len(data.Symbols) != ExpectedSymbolsCount {
|
|
|
|
t.Errorf("got %v symbols expected %v", len(data.Symbols), ExpectedSymbolsCount)
|
|
|
|
}
|
|
|
|
tests.Symbol(t, data.Symbols)
|
|
|
|
})
|
|
|
|
|
2019-05-14 19:20:41 -06:00
|
|
|
t.Run("SignatureHelp", func(t *testing.T) {
|
2019-04-16 13:47:48 -06:00
|
|
|
t.Helper()
|
|
|
|
if len(data.Signatures) != ExpectedSignaturesCount {
|
|
|
|
t.Errorf("got %v signatures expected %v", len(data.Signatures), ExpectedSignaturesCount)
|
|
|
|
}
|
2019-05-14 19:20:41 -06:00
|
|
|
tests.SignatureHelp(t, data.Signatures)
|
2019-04-16 13:47:48 -06:00
|
|
|
})
|
2019-04-24 09:33:45 -06:00
|
|
|
|
2019-04-27 20:45:06 -06:00
|
|
|
t.Run("Link", func(t *testing.T) {
|
2019-04-24 09:33:45 -06:00
|
|
|
t.Helper()
|
|
|
|
linksCount := 0
|
|
|
|
for _, want := range data.Links {
|
|
|
|
linksCount += len(want)
|
|
|
|
}
|
|
|
|
if linksCount != ExpectedLinksCount {
|
|
|
|
t.Errorf("got %v links expected %v", linksCount, ExpectedLinksCount)
|
|
|
|
}
|
|
|
|
tests.Link(t, data.Links)
|
|
|
|
})
|
2019-05-01 17:16:03 -06:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
2019-05-01 17:16:03 -06:00
|
|
|
func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte {
|
2019-04-09 20:23:02 -06:00
|
|
|
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
|
|
|
|
}
|
2019-05-01 17:16:03 -06:00
|
|
|
golden := data.golden[fragment]
|
|
|
|
if golden == nil {
|
|
|
|
if !*updateGolden {
|
|
|
|
data.t.Fatalf("could not find golden file %v: %v", fragment, tag)
|
|
|
|
}
|
|
|
|
golden = &Golden{
|
|
|
|
Filename: filepath.Join(data.dir, 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
|
|
|
|
}
|
2019-04-09 20:23:02 -06:00
|
|
|
}
|
|
|
|
if *updateGolden {
|
2019-05-01 17:16:03 -06:00
|
|
|
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)
|
2019-04-09 20:23:02 -06:00
|
|
|
}
|
2019-05-01 17:16:03 -06:00
|
|
|
file.Data = append(contents, '\n') // add trailing \n for txtar
|
|
|
|
golden.Modified = true
|
2019-04-09 20:23:02 -06:00
|
|
|
}
|
2019-05-01 17:16:03 -06:00
|
|
|
if file == nil {
|
|
|
|
data.t.Fatalf("could not find golden contents %v: %v", fragment, tag)
|
2019-04-09 20:23:02 -06:00
|
|
|
}
|
2019-05-01 17:16:03 -06:00
|
|
|
return file.Data[:len(file.Data)-1] // drop the trailing \n
|
2019-04-09 20:23:02 -06:00
|
|
|
}
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
|
|
|
|
if _, ok := data.Diagnostics[spn.URI()]; !ok {
|
|
|
|
data.Diagnostics[spn.URI()] = []source.Diagnostic{}
|
|
|
|
}
|
|
|
|
severity := source.SeverityError
|
|
|
|
if strings.Contains(string(spn.URI()), "analyzer") {
|
|
|
|
severity = source.SeverityWarning
|
|
|
|
}
|
2019-08-14 18:12:18 -06:00
|
|
|
// This is not the correct way to do this,
|
|
|
|
// but it seems excessive to do the full conversion here.
|
2019-04-16 13:47:48 -06:00
|
|
|
want := source.Diagnostic{
|
2019-08-14 18:12:18 -06:00
|
|
|
URI: spn.URI(),
|
|
|
|
Range: protocol.Range{
|
|
|
|
Start: protocol.Position{
|
|
|
|
Line: float64(spn.Start().Line()) - 1,
|
|
|
|
Character: float64(spn.Start().Column()) - 1,
|
|
|
|
},
|
|
|
|
End: protocol.Position{
|
|
|
|
Line: float64(spn.End().Line()) - 1,
|
|
|
|
Character: float64(spn.End().Column()) - 1,
|
|
|
|
},
|
|
|
|
},
|
2019-04-16 13:47:48 -06:00
|
|
|
Severity: severity,
|
|
|
|
Source: msgSource,
|
|
|
|
Message: msg,
|
|
|
|
}
|
|
|
|
data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
|
|
|
|
}
|
|
|
|
|
2019-08-14 18:12:18 -06:00
|
|
|
// diffDiagnostics prints the diff between expected and actual diagnostics test
|
|
|
|
// results.
|
|
|
|
func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
|
|
|
|
sortDiagnostics(want)
|
|
|
|
sortDiagnostics(got)
|
|
|
|
|
|
|
|
if len(got) != len(want) {
|
|
|
|
return summarizeDiagnostics(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
|
|
|
}
|
|
|
|
for i, w := range want {
|
|
|
|
g := got[i]
|
|
|
|
if w.Message != g.Message {
|
|
|
|
return summarizeDiagnostics(i, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
|
|
|
|
}
|
|
|
|
if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
|
|
|
|
return summarizeDiagnostics(i, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
|
|
|
|
}
|
|
|
|
// Special case for diagnostics on parse errors.
|
|
|
|
if strings.Contains(string(uri), "noparse") {
|
|
|
|
if protocol.ComparePosition(g.Range.Start, g.Range.End) != 0 || protocol.ComparePosition(w.Range.Start, g.Range.End) != 0 {
|
|
|
|
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.Start)
|
|
|
|
}
|
|
|
|
} else if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
|
|
|
|
if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
|
|
|
|
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if w.Severity != g.Severity {
|
|
|
|
return summarizeDiagnostics(i, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
|
|
|
|
}
|
|
|
|
if w.Source != g.Source {
|
|
|
|
return summarizeDiagnostics(i, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortDiagnostics(d []source.Diagnostic) {
|
|
|
|
sort.Slice(d, func(i int, j int) bool {
|
|
|
|
return compareDiagnostic(d[i], d[j]) < 0
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func compareDiagnostic(a, b source.Diagnostic) int {
|
|
|
|
if r := span.CompareURI(a.URI, b.URI); r != 0 {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
if a.Message < b.Message {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if a.Message == b.Message {
|
|
|
|
return 0
|
|
|
|
} else {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func summarizeDiagnostics(i int, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string {
|
|
|
|
msg := &bytes.Buffer{}
|
|
|
|
fmt.Fprint(msg, "diagnostics failed")
|
|
|
|
if i >= 0 {
|
|
|
|
fmt.Fprintf(msg, " at %d", i)
|
|
|
|
}
|
|
|
|
fmt.Fprint(msg, " because of ")
|
|
|
|
fmt.Fprintf(msg, reason, args...)
|
|
|
|
fmt.Fprint(msg, ":\nexpected:\n")
|
|
|
|
for _, d := range want {
|
|
|
|
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(msg, "got:\n")
|
|
|
|
for _, d := range got {
|
|
|
|
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
|
|
|
}
|
|
|
|
return msg.String()
|
|
|
|
}
|
|
|
|
|
2019-06-17 21:56:06 -06:00
|
|
|
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
|
|
|
|
return func(src span.Span, expected []token.Pos) {
|
|
|
|
data.Completions[src] = Completion{
|
|
|
|
CompletionItems: expected,
|
|
|
|
Type: typ,
|
|
|
|
}
|
|
|
|
}
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
2019-08-06 16:51:17 -06:00
|
|
|
func (data *Data) collectCompletionItems(pos token.Pos, args []string) {
|
|
|
|
if len(args) < 3 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
label, detail, kind := args[0], args[1], args[2]
|
|
|
|
var documentation string
|
|
|
|
if len(args) == 4 {
|
|
|
|
documentation = args[3]
|
|
|
|
}
|
2019-04-16 13:47:48 -06:00
|
|
|
data.CompletionItems[pos] = &source.CompletionItem{
|
2019-08-06 16:51:17 -06:00
|
|
|
Label: label,
|
|
|
|
Detail: detail,
|
|
|
|
Kind: source.ParseCompletionItemKind(kind),
|
|
|
|
Documentation: documentation,
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-28 19:48:29 -06:00
|
|
|
func (data *Data) collectFoldingRanges(spn span.Span) {
|
|
|
|
data.FoldingRanges = append(data.FoldingRanges, spn)
|
|
|
|
}
|
|
|
|
|
2019-04-22 16:15:39 -06:00
|
|
|
func (data *Data) collectFormats(spn span.Span) {
|
|
|
|
data.Formats = append(data.Formats, spn)
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
|
2019-05-31 20:42:59 -06:00
|
|
|
func (data *Data) collectImports(spn span.Span) {
|
|
|
|
data.Imports = append(data.Imports, spn)
|
|
|
|
}
|
|
|
|
|
2019-09-04 11:16:09 -06:00
|
|
|
func (data *Data) collectSuggestedFixes(spn span.Span) {
|
|
|
|
data.SuggestedFixes = append(data.SuggestedFixes, spn)
|
|
|
|
}
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
func (data *Data) collectDefinitions(src, target span.Span) {
|
|
|
|
data.Definitions[src] = Definition{
|
|
|
|
Src: src,
|
|
|
|
Def: target,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-15 15:58:16 -06:00
|
|
|
func (data *Data) collectHoverDefinitions(src, target span.Span) {
|
|
|
|
data.Definitions[src] = Definition{
|
|
|
|
Src: src,
|
|
|
|
Def: target,
|
|
|
|
OnlyHover: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
func (data *Data) collectTypeDefinitions(src, target span.Span) {
|
|
|
|
data.Definitions[src] = Definition{
|
|
|
|
Src: src,
|
|
|
|
Def: target,
|
|
|
|
IsType: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-01 17:16:03 -06:00
|
|
|
func (data *Data) collectDefinitionNames(src span.Span, name string) {
|
|
|
|
d := data.Definitions[src]
|
|
|
|
d.Name = name
|
|
|
|
data.Definitions[src] = d
|
|
|
|
}
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
func (data *Data) collectHighlights(name string, rng span.Span) {
|
|
|
|
data.Highlights[name] = append(data.Highlights[name], rng)
|
|
|
|
}
|
|
|
|
|
2019-06-07 08:04:22 -06:00
|
|
|
func (data *Data) collectReferences(src span.Span, expected []span.Span) {
|
|
|
|
data.References[src] = expected
|
|
|
|
}
|
|
|
|
|
2019-06-18 08:23:37 -06:00
|
|
|
func (data *Data) collectRenames(src span.Span, newText string) {
|
|
|
|
data.Renames[src] = newText
|
|
|
|
}
|
|
|
|
|
2019-08-22 11:31:03 -06:00
|
|
|
func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placeholder string) {
|
|
|
|
if int(rng.End-rng.Start) != len(placeholder) {
|
|
|
|
// If the length of the placeholder and the length of the range do not match,
|
|
|
|
// make the range just be the start.
|
|
|
|
rng = span.NewRange(rng.FileSet, rng.Start, rng.Start)
|
|
|
|
}
|
2019-09-05 18:04:28 -06:00
|
|
|
contents, err := data.Exported.FileContents(src.URI().Filename())
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
m := protocol.NewColumnMapper(src.URI(), src.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
|
2019-08-22 11:31:03 -06:00
|
|
|
|
2019-09-05 18:04:28 -06:00
|
|
|
// Convert range to span and then to protocol.Range.
|
|
|
|
spn, err := rng.Span()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
prng, err := m.Range(spn)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2019-08-22 11:31:03 -06:00
|
|
|
data.PrepareRenames[src] = &source.PrepareItem{
|
2019-09-05 18:04:28 -06:00
|
|
|
Range: prng,
|
2019-08-22 11:31:03 -06:00
|
|
|
Text: placeholder,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-16 13:47:48 -06:00
|
|
|
func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string) {
|
2019-09-05 16:54:05 -06:00
|
|
|
contents, err := data.Exported.FileContents(spn.URI().Filename())
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
m := protocol.NewColumnMapper(spn.URI(), spn.URI().Filename(), data.Exported.ExpectFileSet, nil, contents)
|
|
|
|
rng, err := m.Range(spn)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sym := protocol.DocumentSymbol{
|
|
|
|
Name: name,
|
|
|
|
Kind: protocol.ParseSymbolKind(kind),
|
|
|
|
SelectionRange: rng,
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
|
|
|
if parentName == "" {
|
|
|
|
data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym)
|
|
|
|
} else {
|
|
|
|
data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) {
|
2019-06-28 19:27:41 -06:00
|
|
|
data.Signatures[spn] = &source.SignatureInformation{
|
2019-04-16 13:47:48 -06:00
|
|
|
Label: signature,
|
|
|
|
ActiveParameter: int(activeParam),
|
|
|
|
}
|
2019-06-28 19:27:41 -06:00
|
|
|
// Hardcode special case to test the lack of a signature.
|
|
|
|
if signature == "" && activeParam == 0 {
|
|
|
|
data.Signatures[spn] = nil
|
|
|
|
}
|
2019-04-16 13:47:48 -06:00
|
|
|
}
|
2019-04-28 21:19:54 -06:00
|
|
|
|
|
|
|
func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) {
|
|
|
|
data.CompletionSnippets[spn] = CompletionSnippet{
|
|
|
|
CompletionItem: item,
|
|
|
|
PlainSnippet: plain,
|
|
|
|
PlaceholderSnippet: placeholder,
|
|
|
|
}
|
|
|
|
}
|
2019-04-24 09:33:45 -06:00
|
|
|
|
2019-07-07 16:25:19 -06:00
|
|
|
func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {
|
|
|
|
position := fset.Position(note.Pos)
|
2019-04-24 09:33:45 -06:00
|
|
|
uri := spn.URI()
|
|
|
|
data.Links[uri] = append(data.Links[uri], Link{
|
2019-07-07 16:25:19 -06:00
|
|
|
Src: spn,
|
|
|
|
Target: link,
|
|
|
|
NotePosition: position,
|
2019-04-24 09:33:45 -06:00
|
|
|
})
|
|
|
|
}
|