2020-02-06 17:50:37 -07:00
|
|
|
// Copyright 2020 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 regtest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2020-04-15 15:14:53 -06:00
|
|
|
"regexp"
|
2020-02-06 17:50:37 -07:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"golang.org/x/tools/internal/jsonrpc2/servertest"
|
|
|
|
"golang.org/x/tools/internal/lsp/fake"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Env holds an initialized fake Editor, Workspace, and Server, which may be
|
|
|
|
// used for writing tests. It also provides adapter methods that call t.Fatal
|
|
|
|
// on any error, so that tests for the happy path may be written without
|
|
|
|
// checking errors.
|
|
|
|
type Env struct {
|
2020-03-23 15:26:05 -06:00
|
|
|
T *testing.T
|
|
|
|
Ctx context.Context
|
2020-02-06 17:50:37 -07:00
|
|
|
|
2020-04-28 22:00:52 -06:00
|
|
|
// Most tests should not need to access the scratch area, editor, server, or
|
2020-03-04 11:29:23 -07:00
|
|
|
// connection, but they are available if needed.
|
2020-04-28 22:00:52 -06:00
|
|
|
Sandbox *fake.Sandbox
|
|
|
|
Editor *fake.Editor
|
|
|
|
Server servertest.Connector
|
2020-02-06 17:50:37 -07:00
|
|
|
|
|
|
|
// mu guards the fields below, for the purpose of checking conditions on
|
|
|
|
// every change to diagnostics.
|
|
|
|
mu sync.Mutex
|
|
|
|
// For simplicity, each waiter gets a unique ID.
|
2020-04-15 15:14:53 -06:00
|
|
|
nextWaiterID int
|
|
|
|
state State
|
|
|
|
waiters map[int]*condition
|
|
|
|
}
|
|
|
|
|
|
|
|
// State encapsulates the server state TODO: explain more
|
|
|
|
type State struct {
|
|
|
|
// diagnostics are a map of relative path->diagnostics params
|
2020-05-28 19:21:29 -06:00
|
|
|
diagnostics map[string]*protocol.PublishDiagnosticsParams
|
|
|
|
logs []*protocol.LogMessageParams
|
|
|
|
showMessage []*protocol.ShowMessageParams
|
|
|
|
showMessageRequest []*protocol.ShowMessageRequestParams
|
2020-04-21 21:44:31 -06:00
|
|
|
// outstandingWork is a map of token->work summary. All tokens are assumed to
|
|
|
|
// be string, though the spec allows for numeric tokens as well. When work
|
|
|
|
// completes, it is deleted from this map.
|
|
|
|
outstandingWork map[string]*workProgress
|
2020-04-22 15:54:30 -06:00
|
|
|
completedWork map[string]int
|
2020-04-21 21:44:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type workProgress struct {
|
|
|
|
title string
|
|
|
|
percent float64
|
2020-04-15 15:14:53 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s State) String() string {
|
|
|
|
var b strings.Builder
|
|
|
|
b.WriteString("#### log messages (see RPC logs for full text):\n")
|
|
|
|
for _, msg := range s.logs {
|
|
|
|
summary := fmt.Sprintf("%v: %q", msg.Type, msg.Message)
|
|
|
|
if len(summary) > 60 {
|
|
|
|
summary = summary[:57] + "..."
|
|
|
|
}
|
|
|
|
// Some logs are quite long, and since they should be reproduced in the RPC
|
|
|
|
// logs on any failure we include here just a short summary.
|
|
|
|
fmt.Fprint(&b, "\t"+summary+"\n")
|
|
|
|
}
|
|
|
|
b.WriteString("\n")
|
|
|
|
b.WriteString("#### diagnostics:\n")
|
|
|
|
for name, params := range s.diagnostics {
|
|
|
|
fmt.Fprintf(&b, "\t%s (version %d):\n", name, int(params.Version))
|
|
|
|
for _, d := range params.Diagnostics {
|
|
|
|
fmt.Fprintf(&b, "\t\t(%d, %d): %s\n", int(d.Range.Start.Line), int(d.Range.Start.Character), d.Message)
|
|
|
|
}
|
|
|
|
}
|
2020-04-21 21:44:31 -06:00
|
|
|
b.WriteString("\n")
|
|
|
|
b.WriteString("#### outstanding work:\n")
|
|
|
|
for token, state := range s.outstandingWork {
|
|
|
|
name := state.title
|
|
|
|
if name == "" {
|
|
|
|
name = fmt.Sprintf("!NO NAME(token: %s)", token)
|
|
|
|
}
|
2020-06-02 16:24:26 -06:00
|
|
|
fmt.Fprintf(&b, "\t%s: %.2f\n", name, state.percent)
|
|
|
|
}
|
|
|
|
b.WriteString("#### completed work:\n")
|
|
|
|
for name, count := range s.completedWork {
|
|
|
|
fmt.Fprintf(&b, "\t%s: %d\n", name, count)
|
2020-04-21 21:44:31 -06:00
|
|
|
}
|
2020-04-15 15:14:53 -06:00
|
|
|
return b.String()
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
// A condition is satisfied when all expectations are simultaneously
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
// met. At that point, the 'met' channel is closed. On any failure, err is set
|
|
|
|
// and the failed channel is closed.
|
2020-04-15 15:14:53 -06:00
|
|
|
type condition struct {
|
|
|
|
expectations []Expectation
|
|
|
|
verdict chan Verdict
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
|
2020-04-28 22:00:52 -06:00
|
|
|
// NewEnv creates a new test environment using the given scratch environment
|
|
|
|
// and gopls server.
|
2020-07-22 19:15:22 -06:00
|
|
|
func NewEnv(ctx context.Context, t *testing.T, sandbox *fake.Sandbox, ts servertest.Connector, editorConfig fake.EditorConfig, withHooks bool) *Env {
|
2020-02-06 17:50:37 -07:00
|
|
|
t.Helper()
|
|
|
|
conn := ts.Connect(ctx)
|
|
|
|
env := &Env{
|
2020-04-28 22:00:52 -06:00
|
|
|
T: t,
|
|
|
|
Ctx: ctx,
|
2020-07-22 19:15:22 -06:00
|
|
|
Sandbox: sandbox,
|
2020-04-28 22:00:52 -06:00
|
|
|
Server: ts,
|
2020-04-15 15:14:53 -06:00
|
|
|
state: State{
|
2020-04-21 21:44:31 -06:00
|
|
|
diagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
|
|
|
|
outstandingWork: make(map[string]*workProgress),
|
2020-04-22 15:54:30 -06:00
|
|
|
completedWork: make(map[string]int),
|
2020-04-15 15:14:53 -06:00
|
|
|
},
|
|
|
|
waiters: make(map[int]*condition),
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
2020-07-22 19:15:22 -06:00
|
|
|
var hooks fake.ClientHooks
|
|
|
|
if withHooks {
|
|
|
|
hooks = fake.ClientHooks{
|
|
|
|
OnDiagnostics: env.onDiagnostics,
|
|
|
|
OnLogMessage: env.onLogMessage,
|
|
|
|
OnWorkDoneProgressCreate: env.onWorkDoneProgressCreate,
|
|
|
|
OnProgress: env.onProgress,
|
|
|
|
OnShowMessage: env.onShowMessage,
|
|
|
|
OnShowMessageRequest: env.onShowMessageRequest,
|
|
|
|
}
|
2020-05-07 10:25:33 -06:00
|
|
|
}
|
2020-07-22 19:15:22 -06:00
|
|
|
editor, err := fake.NewEditor(sandbox, editorConfig).Connect(ctx, conn, hooks)
|
2020-05-07 10:25:33 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
env.Editor = editor
|
2020-02-06 17:50:37 -07:00
|
|
|
return env
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Env) onDiagnostics(_ context.Context, d *protocol.PublishDiagnosticsParams) error {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
2020-04-28 22:00:52 -06:00
|
|
|
pth := e.Sandbox.Workdir.URIToPath(d.URI)
|
2020-04-15 15:14:53 -06:00
|
|
|
e.state.diagnostics[pth] = d
|
|
|
|
e.checkConditionsLocked()
|
|
|
|
return nil
|
|
|
|
}
|
2020-02-06 17:50:37 -07:00
|
|
|
|
2020-04-29 11:33:43 -06:00
|
|
|
func (e *Env) onShowMessage(_ context.Context, m *protocol.ShowMessageParams) error {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
|
|
|
e.state.showMessage = append(e.state.showMessage, m)
|
|
|
|
e.checkConditionsLocked()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-28 19:21:29 -06:00
|
|
|
func (e *Env) onShowMessageRequest(_ context.Context, m *protocol.ShowMessageRequestParams) error {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
|
|
|
e.state.showMessageRequest = append(e.state.showMessageRequest, m)
|
|
|
|
e.checkConditionsLocked()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
func (e *Env) onLogMessage(_ context.Context, m *protocol.LogMessageParams) error {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
2020-04-22 15:54:30 -06:00
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
e.state.logs = append(e.state.logs, m)
|
|
|
|
e.checkConditionsLocked()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-21 21:44:31 -06:00
|
|
|
func (e *Env) onWorkDoneProgressCreate(_ context.Context, m *protocol.WorkDoneProgressCreateParams) error {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
2020-04-22 15:54:30 -06:00
|
|
|
|
2020-04-21 21:44:31 -06:00
|
|
|
token := m.Token.(string)
|
|
|
|
e.state.outstandingWork[token] = &workProgress{}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Env) onProgress(_ context.Context, m *protocol.ProgressParams) error {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
token := m.Token.(string)
|
|
|
|
work, ok := e.state.outstandingWork[token]
|
|
|
|
if !ok {
|
2020-04-22 15:54:30 -06:00
|
|
|
panic(fmt.Sprintf("got progress report for unknown report %s: %v", token, m))
|
2020-04-21 21:44:31 -06:00
|
|
|
}
|
|
|
|
v := m.Value.(map[string]interface{})
|
|
|
|
switch kind := v["kind"]; kind {
|
|
|
|
case "begin":
|
|
|
|
work.title = v["title"].(string)
|
|
|
|
case "report":
|
|
|
|
if pct, ok := v["percentage"]; ok {
|
|
|
|
work.percent = pct.(float64)
|
|
|
|
}
|
|
|
|
case "end":
|
2020-04-22 15:54:30 -06:00
|
|
|
title := e.state.outstandingWork[token].title
|
|
|
|
e.state.completedWork[title] = e.state.completedWork[title] + 1
|
2020-04-21 21:44:31 -06:00
|
|
|
delete(e.state.outstandingWork, token)
|
|
|
|
}
|
|
|
|
e.checkConditionsLocked()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
func (e *Env) checkConditionsLocked() {
|
2020-02-06 17:50:37 -07:00
|
|
|
for id, condition := range e.waiters {
|
2020-04-01 19:31:43 -06:00
|
|
|
if v, _, _ := checkExpectations(e.state, condition.expectations); v != Unmet {
|
2020-02-06 17:50:37 -07:00
|
|
|
delete(e.waiters, id)
|
2020-04-15 15:14:53 -06:00
|
|
|
condition.verdict <- v
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
// ExpectNow asserts that the current state of the editor matches the given
|
|
|
|
// expectations.
|
|
|
|
//
|
|
|
|
// It can be used together with Env.Await to allow waiting on
|
|
|
|
// simple expectations, followed by more detailed expectations tested by
|
|
|
|
// ExpectNow. For example:
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
//
|
|
|
|
// env.RegexpReplace("foo.go", "a", "x")
|
|
|
|
// env.Await(env.AnyDiagnosticAtCurrentVersion("foo.go"))
|
2020-04-15 15:14:53 -06:00
|
|
|
// env.ExpectNow(env.DiagnosticAtRegexp("foo.go", "x"))
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
//
|
|
|
|
// This has the advantage of not timing out if the diagnostic received for
|
|
|
|
// "foo.go" does not match the expectation: instead it fails early.
|
2020-04-15 15:14:53 -06:00
|
|
|
func (e *Env) ExpectNow(expectations ...Expectation) {
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
e.T.Helper()
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
2020-04-01 19:31:43 -06:00
|
|
|
if verdict, summary, _ := checkExpectations(e.state, expectations); verdict != Met {
|
2020-04-15 15:14:53 -06:00
|
|
|
e.T.Fatalf("expectations unmet:\n%s\ncurrent state:\n%v", summary, e.state)
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
// checkExpectations reports whether s meets all expectations.
|
2020-04-01 19:31:43 -06:00
|
|
|
func checkExpectations(s State, expectations []Expectation) (Verdict, string, []interface{}) {
|
2020-04-15 15:14:53 -06:00
|
|
|
finalVerdict := Met
|
2020-04-01 19:31:43 -06:00
|
|
|
var metBy []interface{}
|
2020-04-15 15:14:53 -06:00
|
|
|
var summary strings.Builder
|
2020-02-06 17:50:37 -07:00
|
|
|
for _, e := range expectations {
|
2020-04-01 19:31:43 -06:00
|
|
|
v, mb := e.Check(s)
|
|
|
|
if v == Met {
|
|
|
|
metBy = append(metBy, mb)
|
|
|
|
}
|
2020-04-15 15:14:53 -06:00
|
|
|
if v > finalVerdict {
|
|
|
|
finalVerdict = v
|
|
|
|
}
|
|
|
|
summary.WriteString(fmt.Sprintf("\t%v: %s\n", v, e.Description()))
|
|
|
|
}
|
2020-04-01 19:31:43 -06:00
|
|
|
return finalVerdict, summary.String(), metBy
|
2020-04-15 15:14:53 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// An Expectation asserts that the state of the editor at a point in time
|
|
|
|
// matches an expected condition. This is used for signaling in tests when
|
|
|
|
// certain conditions in the editor are met.
|
|
|
|
type Expectation interface {
|
|
|
|
// Check determines whether the state of the editor satisfies the
|
2020-04-01 19:31:43 -06:00
|
|
|
// expectation, returning the results that met the condition.
|
|
|
|
Check(State) (Verdict, interface{})
|
2020-04-15 15:14:53 -06:00
|
|
|
// Description is a human-readable description of the expectation.
|
|
|
|
Description() string
|
|
|
|
}
|
|
|
|
|
|
|
|
// A Verdict is the result of checking an expectation against the current
|
|
|
|
// editor state.
|
|
|
|
type Verdict int
|
|
|
|
|
|
|
|
// Order matters for the following constants: verdicts are sorted in order of
|
|
|
|
// decisiveness.
|
|
|
|
const (
|
|
|
|
// Met indicates that an expectation is satisfied by the current state.
|
|
|
|
Met Verdict = iota
|
|
|
|
// Unmet indicates that an expectation is not currently met, but could be met
|
|
|
|
// in the future.
|
|
|
|
Unmet
|
|
|
|
// Unmeetable indicates that an expectation cannot be satisfied in the
|
|
|
|
// future.
|
|
|
|
Unmeetable
|
|
|
|
)
|
|
|
|
|
2020-04-24 12:22:13 -06:00
|
|
|
// OnceMet returns an Expectation that, once the precondition is met, asserts
|
|
|
|
// that mustMeet is met.
|
|
|
|
func OnceMet(precondition Expectation, mustMeet Expectation) *SimpleExpectation {
|
|
|
|
check := func(s State) (Verdict, interface{}) {
|
|
|
|
switch pre, _ := precondition.Check(s); pre {
|
|
|
|
case Unmeetable:
|
|
|
|
return Unmeetable, nil
|
|
|
|
case Met:
|
|
|
|
verdict, metBy := mustMeet.Check(s)
|
|
|
|
if verdict != Met {
|
|
|
|
return Unmeetable, metBy
|
|
|
|
}
|
|
|
|
return Met, metBy
|
|
|
|
default:
|
|
|
|
return Unmet, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &SimpleExpectation{
|
|
|
|
check: check,
|
|
|
|
description: fmt.Sprintf("once %q is met, must have %q", precondition.Description(), mustMeet.Description()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
func (v Verdict) String() string {
|
|
|
|
switch v {
|
|
|
|
case Met:
|
|
|
|
return "Met"
|
|
|
|
case Unmet:
|
|
|
|
return "Unmet"
|
|
|
|
case Unmeetable:
|
|
|
|
return "Unmeetable"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("unrecognized verdict %d", v)
|
|
|
|
}
|
|
|
|
|
2020-04-21 21:44:31 -06:00
|
|
|
// SimpleExpectation holds an arbitrary check func, and implements the Expectation interface.
|
|
|
|
type SimpleExpectation struct {
|
2020-04-22 15:54:30 -06:00
|
|
|
check func(State) (Verdict, interface{})
|
2020-04-21 21:44:31 -06:00
|
|
|
description string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check invokes e.check.
|
2020-04-22 15:54:30 -06:00
|
|
|
func (e SimpleExpectation) Check(s State) (Verdict, interface{}) {
|
2020-04-21 21:44:31 -06:00
|
|
|
return e.check(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Description returns e.descriptin.
|
|
|
|
func (e SimpleExpectation) Description() string {
|
|
|
|
return e.description
|
|
|
|
}
|
|
|
|
|
|
|
|
// NoOutstandingWork asserts that there is no work initiated using the LSP
|
|
|
|
// $/progress API that has not completed.
|
|
|
|
func NoOutstandingWork() SimpleExpectation {
|
2020-04-22 15:54:30 -06:00
|
|
|
check := func(s State) (Verdict, interface{}) {
|
2020-04-21 21:44:31 -06:00
|
|
|
if len(s.outstandingWork) == 0 {
|
2020-04-22 15:54:30 -06:00
|
|
|
return Met, nil
|
2020-04-21 21:44:31 -06:00
|
|
|
}
|
2020-04-22 15:54:30 -06:00
|
|
|
return Unmet, nil
|
2020-04-21 21:44:31 -06:00
|
|
|
}
|
|
|
|
return SimpleExpectation{
|
|
|
|
check: check,
|
|
|
|
description: "no outstanding work",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-29 11:33:43 -06:00
|
|
|
// EmptyShowMessage asserts that the editor has not received a ShowMessage.
|
|
|
|
func EmptyShowMessage(title string) SimpleExpectation {
|
|
|
|
check := func(s State) (Verdict, interface{}) {
|
|
|
|
if len(s.showMessage) == 0 {
|
|
|
|
return Met, title
|
|
|
|
}
|
|
|
|
return Unmeetable, nil
|
|
|
|
}
|
|
|
|
return SimpleExpectation{
|
|
|
|
check: check,
|
|
|
|
description: "no ShowMessage received",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
// SomeShowMessage asserts that the editor has received a ShowMessage with the given title.
|
2020-04-29 11:33:43 -06:00
|
|
|
func SomeShowMessage(title string) SimpleExpectation {
|
|
|
|
check := func(s State) (Verdict, interface{}) {
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
for _, m := range s.showMessage {
|
|
|
|
if strings.Contains(m.Message, title) {
|
|
|
|
return Met, m
|
|
|
|
}
|
2020-04-29 11:33:43 -06:00
|
|
|
}
|
|
|
|
return Unmet, nil
|
|
|
|
}
|
|
|
|
return SimpleExpectation{
|
|
|
|
check: check,
|
|
|
|
description: "received ShowMessage",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-28 19:21:29 -06:00
|
|
|
// ShowMessageRequest asserts that the editor has received a ShowMessageRequest
|
|
|
|
// with an action item that has the given title.
|
|
|
|
func ShowMessageRequest(title string) SimpleExpectation {
|
|
|
|
check := func(s State) (Verdict, interface{}) {
|
|
|
|
if len(s.showMessageRequest) == 0 {
|
|
|
|
return Unmet, nil
|
|
|
|
}
|
|
|
|
// Only check the most recent one.
|
|
|
|
m := s.showMessageRequest[len(s.showMessageRequest)-1]
|
|
|
|
if len(m.Actions) == 0 || len(m.Actions) > 1 {
|
|
|
|
return Unmet, nil
|
|
|
|
}
|
|
|
|
if m.Actions[0].Title == title {
|
|
|
|
return Met, m.Actions[0]
|
|
|
|
}
|
|
|
|
return Unmet, nil
|
|
|
|
}
|
|
|
|
return SimpleExpectation{
|
|
|
|
check: check,
|
|
|
|
description: "received ShowMessageRequest",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-22 15:54:30 -06:00
|
|
|
// CompletedWork expects a work item to have been completed >= atLeast times.
|
|
|
|
//
|
|
|
|
// Since the Progress API doesn't include any hidden metadata, we must use the
|
|
|
|
// progress notification title to identify the work we expect to be completed.
|
|
|
|
func CompletedWork(title string, atLeast int) SimpleExpectation {
|
|
|
|
check := func(s State) (Verdict, interface{}) {
|
|
|
|
if s.completedWork[title] >= atLeast {
|
|
|
|
return Met, title
|
|
|
|
}
|
|
|
|
return Unmet, nil
|
|
|
|
}
|
|
|
|
return SimpleExpectation{
|
|
|
|
check: check,
|
|
|
|
description: fmt.Sprintf("completed work %q at least %d time(s)", title, atLeast),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
// LogExpectation is an expectation on the log messages received by the editor
|
|
|
|
// from gopls.
|
|
|
|
type LogExpectation struct {
|
2020-04-01 19:31:43 -06:00
|
|
|
check func([]*protocol.LogMessageParams) (Verdict, interface{})
|
2020-04-15 15:14:53 -06:00
|
|
|
description string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check implements the Expectation interface.
|
2020-04-01 19:31:43 -06:00
|
|
|
func (e LogExpectation) Check(s State) (Verdict, interface{}) {
|
2020-04-15 15:14:53 -06:00
|
|
|
return e.check(s.logs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Description implements the Expectation interface.
|
|
|
|
func (e LogExpectation) Description() string {
|
|
|
|
return e.description
|
|
|
|
}
|
|
|
|
|
|
|
|
// NoErrorLogs asserts that the client has not received any log messages of
|
|
|
|
// error severity.
|
|
|
|
func NoErrorLogs() LogExpectation {
|
2020-06-18 23:25:24 -06:00
|
|
|
return NoLogMatching(protocol.Error, "")
|
2020-04-15 15:14:53 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// LogMatching asserts that the client has received a log message
|
2020-06-18 23:25:24 -06:00
|
|
|
// of type typ matching the regexp re.
|
2020-04-15 15:14:53 -06:00
|
|
|
func LogMatching(typ protocol.MessageType, re string) LogExpectation {
|
|
|
|
rec, err := regexp.Compile(re)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-04-01 19:31:43 -06:00
|
|
|
check := func(msgs []*protocol.LogMessageParams) (Verdict, interface{}) {
|
2020-04-15 15:14:53 -06:00
|
|
|
for _, msg := range msgs {
|
|
|
|
if msg.Type == typ && rec.Match([]byte(msg.Message)) {
|
2020-04-01 19:31:43 -06:00
|
|
|
return Met, msg
|
2020-04-15 15:14:53 -06:00
|
|
|
}
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
2020-04-01 19:31:43 -06:00
|
|
|
return Unmet, nil
|
2020-04-15 15:14:53 -06:00
|
|
|
}
|
|
|
|
return LogExpectation{
|
|
|
|
check: check,
|
|
|
|
description: fmt.Sprintf("log message matching %q", re),
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-18 23:25:24 -06:00
|
|
|
// NoLogMatching asserts that the client has not received a log message
|
|
|
|
// of type typ matching the regexp re. If re is an empty string, any log
|
|
|
|
// message is considered a match.
|
|
|
|
func NoLogMatching(typ protocol.MessageType, re string) LogExpectation {
|
|
|
|
var r *regexp.Regexp
|
|
|
|
if re != "" {
|
|
|
|
var err error
|
|
|
|
r, err = regexp.Compile(re)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
check := func(msgs []*protocol.LogMessageParams) (Verdict, interface{}) {
|
|
|
|
for _, msg := range msgs {
|
|
|
|
if msg.Type != typ {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if r == nil || r.Match([]byte(msg.Message)) {
|
|
|
|
return Unmeetable, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Met, nil
|
|
|
|
}
|
|
|
|
return LogExpectation{
|
|
|
|
check: check,
|
|
|
|
description: fmt.Sprintf("no log message matching %q", re),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 17:50:37 -07:00
|
|
|
// A DiagnosticExpectation is a condition that must be met by the current set
|
2020-04-15 15:14:53 -06:00
|
|
|
// of diagnostics for a file.
|
2020-02-06 17:50:37 -07:00
|
|
|
type DiagnosticExpectation struct {
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
// IsMet determines whether the diagnostics for this file version satisfy our
|
|
|
|
// expectation.
|
2020-04-15 15:14:53 -06:00
|
|
|
isMet func(*protocol.PublishDiagnosticsParams) bool
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
// Description is a human-readable description of the diagnostic expectation.
|
2020-04-15 15:14:53 -06:00
|
|
|
description string
|
2020-04-28 22:00:52 -06:00
|
|
|
// Path is the scratch workdir-relative path to the file being asserted on.
|
2020-04-15 15:14:53 -06:00
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check implements the Expectation interface.
|
2020-04-01 19:31:43 -06:00
|
|
|
func (e DiagnosticExpectation) Check(s State) (Verdict, interface{}) {
|
2020-04-15 15:14:53 -06:00
|
|
|
if diags, ok := s.diagnostics[e.path]; ok && e.isMet(diags) {
|
2020-04-01 19:31:43 -06:00
|
|
|
return Met, diags
|
2020-04-15 15:14:53 -06:00
|
|
|
}
|
2020-04-01 19:31:43 -06:00
|
|
|
return Unmet, nil
|
2020-04-15 15:14:53 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Description implements the Expectation interface.
|
|
|
|
func (e DiagnosticExpectation) Description() string {
|
|
|
|
return fmt.Sprintf("%s: %s", e.path, e.description)
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
|
2020-04-23 21:24:24 -06:00
|
|
|
// EmptyDiagnostics asserts that empty diagnostics are sent for the
|
2020-02-06 17:50:37 -07:00
|
|
|
// workspace-relative path name.
|
2020-04-22 15:54:30 -06:00
|
|
|
func EmptyDiagnostics(name string) Expectation {
|
|
|
|
check := func(s State) (Verdict, interface{}) {
|
2020-04-23 21:24:24 -06:00
|
|
|
if diags := s.diagnostics[name]; diags != nil && len(diags.Diagnostics) == 0 {
|
|
|
|
return Met, nil
|
|
|
|
}
|
|
|
|
return Unmet, nil
|
|
|
|
}
|
|
|
|
return SimpleExpectation{
|
|
|
|
check: check,
|
|
|
|
description: "empty diagnostics",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NoDiagnostics asserts that no diagnostics are sent for the
|
2020-05-08 16:19:30 -06:00
|
|
|
// workspace-relative path name. It should be used primarily in conjunction
|
|
|
|
// with a OnceMet, as it has to check that all outstanding diagnostics have
|
|
|
|
// already been delivered.
|
2020-04-23 21:24:24 -06:00
|
|
|
func NoDiagnostics(name string) Expectation {
|
|
|
|
check := func(s State) (Verdict, interface{}) {
|
|
|
|
if _, ok := s.diagnostics[name]; !ok {
|
2020-04-22 15:54:30 -06:00
|
|
|
return Met, nil
|
|
|
|
}
|
|
|
|
return Unmet, nil
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
2020-04-22 15:54:30 -06:00
|
|
|
return SimpleExpectation{
|
|
|
|
check: check,
|
2020-05-08 16:19:30 -06:00
|
|
|
description: "no diagnostics",
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AnyDiagnosticAtCurrentVersion asserts that there is a diagnostic report for
|
|
|
|
// the current edited version of the buffer corresponding to the given
|
2020-04-28 22:00:52 -06:00
|
|
|
// workdir-relative pathname.
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
func (e *Env) AnyDiagnosticAtCurrentVersion(name string) DiagnosticExpectation {
|
2020-04-28 22:00:52 -06:00
|
|
|
version := e.Editor.BufferVersion(name)
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
isMet := func(diags *protocol.PublishDiagnosticsParams) bool {
|
|
|
|
return int(diags.Version) == version
|
|
|
|
}
|
|
|
|
return DiagnosticExpectation{
|
2020-04-15 15:14:53 -06:00
|
|
|
isMet: isMet,
|
|
|
|
description: fmt.Sprintf("any diagnostics at version %d", version),
|
|
|
|
path: name,
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-28 12:56:20 -07:00
|
|
|
// DiagnosticAtRegexp expects that there is a diagnostic entry at the start
|
|
|
|
// position matching the regexp search string re in the buffer specified by
|
|
|
|
// name. Note that this currently ignores the end position.
|
|
|
|
func (e *Env) DiagnosticAtRegexp(name, re string) DiagnosticExpectation {
|
2020-05-14 21:15:12 -06:00
|
|
|
e.T.Helper()
|
2020-02-28 12:56:20 -07:00
|
|
|
pos := e.RegexpSearch(name, re)
|
|
|
|
expectation := DiagnosticAt(name, pos.Line, pos.Column)
|
2020-04-15 15:14:53 -06:00
|
|
|
expectation.description += fmt.Sprintf(" (location of %q)", re)
|
2020-02-28 12:56:20 -07:00
|
|
|
return expectation
|
|
|
|
}
|
|
|
|
|
2020-02-06 17:50:37 -07:00
|
|
|
// DiagnosticAt asserts that there is a diagnostic entry at the position
|
2020-04-28 22:00:52 -06:00
|
|
|
// specified by line and col, for the workdir-relative path name.
|
2020-02-06 17:50:37 -07:00
|
|
|
func DiagnosticAt(name string, line, col int) DiagnosticExpectation {
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
isMet := func(diags *protocol.PublishDiagnosticsParams) bool {
|
|
|
|
for _, d := range diags.Diagnostics {
|
2020-02-06 17:50:37 -07:00
|
|
|
if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return DiagnosticExpectation{
|
2020-04-15 15:14:53 -06:00
|
|
|
isMet: isMet,
|
|
|
|
description: fmt.Sprintf("diagnostic at {line:%d, column:%d}", line, col),
|
|
|
|
path: name,
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-06 05:47:14 -06:00
|
|
|
// DiagnosticsFor returns the current diagnostics for the file. It is useful
|
|
|
|
// after waiting on AnyDiagnosticAtCurrentVersion, when the desired diagnostic
|
|
|
|
// is not simply described by DiagnosticAt.
|
|
|
|
func (e *Env) DiagnosticsFor(name string) *protocol.PublishDiagnosticsParams {
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
return e.state.diagnostics[name]
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
// Await waits for all expectations to simultaneously be met. It should only be
|
|
|
|
// called from the main test goroutine.
|
2020-04-01 19:31:43 -06:00
|
|
|
func (e *Env) Await(expectations ...Expectation) []interface{} {
|
2020-03-23 15:26:05 -06:00
|
|
|
e.T.Helper()
|
2020-02-06 17:50:37 -07:00
|
|
|
e.mu.Lock()
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
// Before adding the waiter, we check if the condition is currently met or
|
|
|
|
// failed to avoid a race where the condition was realized before Await was
|
|
|
|
// called.
|
2020-04-01 19:31:43 -06:00
|
|
|
switch verdict, summary, metBy := checkExpectations(e.state, expectations); verdict {
|
2020-04-15 15:14:53 -06:00
|
|
|
case Met:
|
2020-04-22 15:54:30 -06:00
|
|
|
e.mu.Unlock()
|
2020-04-01 19:31:43 -06:00
|
|
|
return metBy
|
2020-04-15 15:14:53 -06:00
|
|
|
case Unmeetable:
|
|
|
|
e.mu.Unlock()
|
|
|
|
e.T.Fatalf("unmeetable expectations:\n%s\nstate:\n%v", summary, e.state)
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
2020-04-15 15:14:53 -06:00
|
|
|
cond := &condition{
|
2020-02-06 17:50:37 -07:00
|
|
|
expectations: expectations,
|
2020-04-15 15:14:53 -06:00
|
|
|
verdict: make(chan Verdict),
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
e.waiters[e.nextWaiterID] = cond
|
2020-02-06 17:50:37 -07:00
|
|
|
e.nextWaiterID++
|
|
|
|
e.mu.Unlock()
|
|
|
|
|
2020-04-15 15:14:53 -06:00
|
|
|
var err error
|
2020-02-06 17:50:37 -07:00
|
|
|
select {
|
2020-03-23 15:26:05 -06:00
|
|
|
case <-e.Ctx.Done():
|
2020-04-15 15:14:53 -06:00
|
|
|
err = e.Ctx.Err()
|
|
|
|
case v := <-cond.verdict:
|
|
|
|
if v != Met {
|
|
|
|
err = fmt.Errorf("condition has final verdict %v", v)
|
|
|
|
}
|
|
|
|
}
|
2020-04-01 19:31:43 -06:00
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
_, summary, metBy := checkExpectations(e.state, expectations)
|
2020-04-15 15:14:53 -06:00
|
|
|
|
2020-04-01 19:31:43 -06:00
|
|
|
// Debugging an unmet expectation can be tricky, so we put some effort into
|
|
|
|
// nicely formatting the failure.
|
2020-04-15 15:14:53 -06:00
|
|
|
if err != nil {
|
2020-04-22 15:54:30 -06:00
|
|
|
e.T.Fatalf("waiting on:\n%s\nerr:%v\n\nstate:\n%v", summary, err, e.state)
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
}
|
2020-04-01 19:31:43 -06:00
|
|
|
return metBy
|
internal/lsp/regtest: add functions to make diagnostic assertions easier
One of the tricky things about asserting on conditions in regtests is
the asynchronous nature of LSP. For example, as the LSP client we cannot
be sure when we've received all diagnostics for a given file.
Currently, regtests are implemented by awaiting specific diagnostic
expectations. This means that if gopls generates diagnostics that do
not match those expectations, we can only time out the test.
Ideally, we would want to know that gopls is done generating all diagnostics
for the current file state. This is not possible without knowing the
status of diagnostics for. Barring this, we would want to know that
diagnostics are done for the current file version. Unfortunately, that
also is not possible, because a new version of file B can affect
diagnostics in file A.
So in lieu of this information, this CL exposes a few tools that can be
used to improve the experience of writing new regtests.
- A new expectation is added: AnyDiagnosticAtCurrentVersion, that is
satisfied if any diagnostics have been received for the current
buffer version.
- ExpectDiagnostics is added to Env, to help check whether the current
diagnostics matches expectations.
Updates golang/go#38113
Change-Id: I48d2c3db87c13ac3ab424d01d9444cbc285af9e1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/226842
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-04-01 12:56:48 -06:00
|
|
|
}
|