go/internal/lsp/lsp_test.go
Ian Cottrell 1a405fd27e internal/lsp: better handling of .in files
This walks the list produced by the MustCopyFileTree call instead of
scanning the file system
It also removes the .in file from the copies, so only the trimmed
version will be present in the exported data set.

Change-Id: I95b0298ab49021a09f6b26e08158ce162b5a99e6
Reviewed-on: https://go-review.googlesource.com/c/149614
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2018-11-15 17:55:04 +00:00

200 lines
5.4 KiB
Go

// Copyright 2018 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 lsp
import (
"context"
"go/token"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"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"
)
func TestLSP(t *testing.T) {
packagestest.TestAll(t, testLSP)
}
func testLSP(t *testing.T, exporter packagestest.Exporter) {
const dir = "testdata"
files := packagestest.MustCopyFileTree(dir)
for fragment, operation := range files {
if trimmed := strings.TrimSuffix(fragment, ".in"); trimmed != fragment {
delete(files, fragment)
files[trimmed] = operation
}
}
modules := []packagestest.Module{
{
Name: "golang.org/x/tools/internal/lsp",
Files: files,
},
}
exported := packagestest.Export(t, exporter, modules)
defer exported.Cleanup()
dirs := make(map[string]bool)
// collect results for certain tests
expectedDiagnostics := make(map[string][]protocol.Diagnostic)
expectedCompletions := make(map[token.Position]*protocol.CompletionItem)
s := &server{
view: source.NewView(),
}
// merge the config objects
cfg := *exported.Config
cfg.Fset = s.view.Config.Fset
cfg.Mode = packages.LoadSyntax
s.view.Config = &cfg
for _, module := range modules {
for fragment := range module.Files {
if !strings.HasSuffix(fragment, ".go") {
continue
}
filename := exporter.Filename(exported, module.Name, fragment)
expectedDiagnostics[filename] = []protocol.Diagnostic{}
dirs[filepath.Dir(filename)] = true
}
}
// Do a first pass to collect special markers
if err := exported.Expect(map[string]interface{}{
"item": func(name string, r packagestest.Range, _, _ string) {
exported.Mark(name, r)
},
}); err != nil {
t.Fatal(err)
}
// Collect any data that needs to be used by subsequent tests.
if err := exported.Expect(map[string]interface{}{
"diag": func(pos token.Position, msg string) {
line := float64(pos.Line - 1)
col := float64(pos.Column - 1)
want := protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: line,
Character: col,
},
End: protocol.Position{
Line: line,
Character: col,
},
},
Severity: protocol.SeverityError,
Source: "LSP",
Message: msg,
}
if _, ok := expectedDiagnostics[pos.Filename]; ok {
expectedDiagnostics[pos.Filename] = append(expectedDiagnostics[pos.Filename], want)
} else {
t.Errorf("unexpected filename: %v", pos.Filename)
}
},
"item": func(pos token.Position, label, detail, kind string) {
var k protocol.CompletionItemKind
switch kind {
case "struct":
k = protocol.StructCompletion
case "func":
k = protocol.FunctionCompletion
case "var":
k = protocol.VariableCompletion
case "type":
k = protocol.TypeParameterCompletion
case "field":
k = protocol.FieldCompletion
case "interface":
k = protocol.InterfaceCompletion
case "const":
k = protocol.ConstantCompletion
case "method":
k = protocol.MethodCompletion
}
expectedCompletions[pos] = &protocol.CompletionItem{
Label: label,
Detail: detail,
Kind: float64(k),
}
},
}); err != nil {
t.Fatal(err)
}
// test completion
testCompletion(t, exported, s, expectedCompletions)
// test diagnostics
var dirList []string
for dir := range dirs {
dirList = append(dirList, dir)
}
exported.Config.Mode = packages.LoadFiles
pkgs, err := packages.Load(exported.Config, dirList...)
if err != nil {
t.Fatal(err)
}
testDiagnostics(t, s.view, pkgs, expectedDiagnostics)
}
func testDiagnostics(t *testing.T, v *source.View, pkgs []*packages.Package, wants map[string][]protocol.Diagnostic) {
for _, pkg := range pkgs {
for _, filename := range pkg.GoFiles {
f := v.GetFile(source.ToURI(filename))
diagnostics, err := source.Diagnostics(context.Background(), v, f)
if err != nil {
t.Fatal(err)
}
got := toProtocolDiagnostics(v, diagnostics[filename])
sort.Slice(got, func(i int, j int) bool {
return got[i].Range.Start.Line < got[j].Range.Start.Line
})
want := wants[filename]
if equal := reflect.DeepEqual(want, got); !equal {
t.Errorf("diagnostics failed for %s: (expected: %v), (got: %v)", filepath.Base(filename), want, got)
}
}
}
}
func testCompletion(t *testing.T, exported *packagestest.Exported, s *server, wants map[token.Position]*protocol.CompletionItem) {
if err := exported.Expect(map[string]interface{}{
"complete": func(src token.Position, expected []token.Position) {
var want []protocol.CompletionItem
for _, pos := range expected {
want = append(want, *wants[pos])
}
list, err := s.Completion(context.Background(), &protocol.CompletionParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.DocumentURI(source.ToURI(src.Filename)),
},
Position: protocol.Position{
Line: float64(src.Line - 1),
Character: float64(src.Column - 1),
},
},
})
if err != nil {
t.Fatal(err)
}
got := list.Items
if equal := reflect.DeepEqual(want, got); !equal {
t.Errorf("completion failed for %s:%v:%v: (expected: %v), (got: %v)", filepath.Base(src.Filename), src.Line, src.Column, want, got)
}
},
}); err != nil {
t.Fatal(err)
}
}