blob: f812eac29a39af3f89e564daddd24fc21df705d2 [file]
// 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 cache
import (
"context"
"encoding/json"
"os"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/gopls/internal/cache/metadata"
"golang.org/x/tools/gopls/internal/file"
"golang.org/x/tools/gopls/internal/protocol"
)
func TestParseErrorMessage(t *testing.T) {
tests := []struct {
name string
in string
expectedFileName string
expectedLine int // (missing => 1)
expectedColumn int // (missing => 1)
}{
{
name: "from go list output",
in: "\nattributes.go:13:1: expected 'package', found 'type'",
expectedFileName: "attributes.go",
expectedLine: 13,
expectedColumn: 1,
},
{
name: "windows driver letter",
in: "C:\\foo\\bar.go:13: message",
expectedFileName: "bar.go",
expectedLine: 13,
expectedColumn: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fn, line, col8 := parseGoListError(packages.Error{Msg: tt.in}, ".")
if !strings.HasSuffix(fn, tt.expectedFileName) {
t.Errorf("expected filename with suffix %v but got %v", tt.expectedFileName, fn)
}
if line != tt.expectedLine {
t.Errorf("expected line %v but got %v", tt.expectedLine, line)
}
if col8 != tt.expectedColumn {
t.Errorf("expected col %v but got %v", tt.expectedLine, col8)
}
})
}
}
func TestDiagnosticEncoding(t *testing.T) {
diags := []*Diagnostic{
{}, // empty
{
URI: "file///foo",
Range: protocol.Range{
Start: protocol.Position{Line: 4, Character: 2},
End: protocol.Position{Line: 6, Character: 7},
},
Severity: protocol.SeverityWarning,
Code: "red",
CodeHref: "https://go.dev",
Source: "test",
Message: "something bad happened",
Tags: []protocol.DiagnosticTag{81},
Related: []protocol.DiagnosticRelatedInformation{
{
Location: protocol.Location{
URI: "file:///other",
Range: protocol.Range{
Start: protocol.Position{Line: 3, Character: 6},
End: protocol.Position{Line: 4, Character: 9},
},
},
Message: "psst, over here",
},
},
// Fields below are used internally to generate quick fixes. They aren't
// part of the LSP spec and don't leave the server.
SuggestedFixes: []SuggestedFix{
{
Title: "fix it!",
Edits: map[protocol.DocumentURI][]protocol.TextEdit{
"file:///foo": {{
Range: protocol.Range{
Start: protocol.Position{Line: 4, Character: 2},
End: protocol.Position{Line: 6, Character: 7},
},
NewText: "abc",
}},
"file:///other": {{
Range: protocol.Range{
Start: protocol.Position{Line: 4, Character: 2},
End: protocol.Position{Line: 6, Character: 7},
},
NewText: "!@#!",
}},
},
Command: &protocol.Command{
Title: "run a command",
Command: "gopls.fix",
Arguments: []json.RawMessage{json.RawMessage(`{"a":1}`)},
},
ActionKind: protocol.QuickFix,
},
},
},
{
URI: "file//bar",
// other fields tested above
},
}
data := encodeDiagnostics(diags)
diags2 := decodeDiagnostics(data)
if diff := cmp.Diff(diags, diags2); diff != "" {
t.Errorf("decoded diagnostics do not match (-original +decoded):\n%s", diff)
}
}
type fakeFileSource map[protocol.DocumentURI][]byte
func (s fakeFileSource) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) {
data, ok := s[uri]
if !ok {
return nil, os.ErrNotExist
}
return makeFakeFileHandle(uri, data), nil
}
func TestParseGoListImportCycleError(t *testing.T) {
const (
fileURI = protocol.DocumentURI("file:///a/a_test.go")
src = `package a
import (
_ "example.com/b"
)
`
)
fs := fakeFileSource{
fileURI: []byte(src),
}
mp := &metadata.Package{
CompiledGoFiles: []protocol.DocumentURI{fileURI},
}
tests := []struct {
name string
errMsg string
wantMsg string
}{
{
name: "standard import cycle",
errMsg: "import cycle not allowed: import stack: [example.com/a example.com/b example.com/a]",
wantMsg: "import cycle not allowed",
},
{
name: "test import cycle",
errMsg: "import cycle not allowed in test: import stack: [example.com/a example.com/b example.com/a]",
wantMsg: "import cycle not allowed in test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diag, err := parseGoListImportCycleError(context.Background(), packages.Error{Msg: tt.errMsg}, mp, fs)
if err != nil {
t.Fatalf("parseGoListImportCycleError failed: %v", err)
}
if diag == nil {
t.Fatalf("expected diagnostic, got nil")
}
if diag.Message != tt.wantMsg {
t.Errorf("Message: want %q, got %q", tt.wantMsg, diag.Message)
}
if diag.Range.Start.Line != 3 { // 0-based line of import _ "example.com/b"
t.Errorf("Range.Start.Line: want 3, got %d", diag.Range.Start.Line)
}
})
}
}