mirror of https://github.com/golang/go.git
internal/lsp/regtest: track outstanding work using the progress API
In preparation for later changes, add support for tracking outstanding work in the lsp regtests. This simply threads through progress notifications and tracks their state in regtest.Env.state. A new Expectation is added to assert that there is no outstanding work, but this is as-yet unused. A unit test is added for Env to check that we're handling work progress reports correctly after Marshaling/Unmarshaling, since we're not yet exercising this code path in actual regtests. Change-Id: I104caf25cfd49340f13d086314f5aef2b8f3bd3b Reviewed-on: https://go-review.googlesource.com/c/tools/+/229320 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
2723c5de0d
commit
38a97e00a8
|
|
@ -10,13 +10,16 @@ import (
|
|||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
)
|
||||
|
||||
// Client is an adapter that converts a *Client into an LSP Client.
|
||||
// Client is an adapter that converts an *Editor into an LSP Client. It mosly
|
||||
// delegates functionality to hooks that can be configured by tests.
|
||||
type Client struct {
|
||||
*Editor
|
||||
|
||||
// Hooks for testing. Add additional hooks here as needed for testing.
|
||||
onLogMessage func(context.Context, *protocol.LogMessageParams) error
|
||||
onDiagnostics func(context.Context, *protocol.PublishDiagnosticsParams) error
|
||||
onLogMessage func(context.Context, *protocol.LogMessageParams) error
|
||||
onDiagnostics func(context.Context, *protocol.PublishDiagnosticsParams) error
|
||||
onWorkDoneProgressCreate func(context.Context, *protocol.WorkDoneProgressCreateParams) error
|
||||
onProgress func(context.Context, *protocol.ProgressParams) error
|
||||
}
|
||||
|
||||
// OnLogMessage sets the hook to run when the editor receives a log message.
|
||||
|
|
@ -34,6 +37,18 @@ func (c *Client) OnDiagnostics(hook func(context.Context, *protocol.PublishDiagn
|
|||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) OnWorkDoneProgressCreate(hook func(context.Context, *protocol.WorkDoneProgressCreateParams) error) {
|
||||
c.mu.Lock()
|
||||
c.onWorkDoneProgressCreate = hook
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) OnProgress(hook func(context.Context, *protocol.ProgressParams) error) {
|
||||
c.mu.Lock()
|
||||
c.onProgress = hook
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) ShowMessage(ctx context.Context, params *protocol.ShowMessageParams) error {
|
||||
c.mu.Lock()
|
||||
c.lastMessage = params
|
||||
|
|
@ -97,11 +112,23 @@ func (c *Client) UnregisterCapability(context.Context, *protocol.UnregistrationP
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Progress(context.Context, *protocol.ProgressParams) error {
|
||||
func (c *Client) Progress(ctx context.Context, params *protocol.ProgressParams) error {
|
||||
c.mu.Lock()
|
||||
onProgress := c.onProgress
|
||||
c.mu.Unlock()
|
||||
if onProgress != nil {
|
||||
return onProgress(ctx, params)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
|
||||
func (c *Client) WorkDoneProgressCreate(ctx context.Context, params *protocol.WorkDoneProgressCreateParams) error {
|
||||
c.mu.Lock()
|
||||
onCreate := c.onWorkDoneProgressCreate
|
||||
c.mu.Unlock()
|
||||
if onCreate != nil {
|
||||
return onCreate(ctx, params)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -346,6 +346,15 @@ type State struct {
|
|||
// diagnostics are a map of relative path->diagnostics params
|
||||
diagnostics map[string]*protocol.PublishDiagnosticsParams
|
||||
logs []*protocol.LogMessageParams
|
||||
// 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
|
||||
}
|
||||
|
||||
type workProgress struct {
|
||||
title string
|
||||
percent float64
|
||||
}
|
||||
|
||||
func (s State) String() string {
|
||||
|
|
@ -368,6 +377,15 @@ func (s State) String() string {
|
|||
fmt.Fprintf(&b, "\t\t(%d, %d): %s\n", int(d.Range.Start.Line), int(d.Range.Start.Character), d.Message)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
fmt.Fprintf(&b, "\t%s: %.2f", name, state.percent)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
|
|
@ -396,12 +414,15 @@ func NewEnv(ctx context.Context, t *testing.T, ws *fake.Workspace, ts servertest
|
|||
Server: ts,
|
||||
Conn: conn,
|
||||
state: State{
|
||||
diagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
|
||||
diagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
|
||||
outstandingWork: make(map[string]*workProgress),
|
||||
},
|
||||
waiters: make(map[int]*condition),
|
||||
}
|
||||
env.E.Client().OnDiagnostics(env.onDiagnostics)
|
||||
env.E.Client().OnLogMessage(env.onLogMessage)
|
||||
env.E.Client().OnWorkDoneProgressCreate(env.onWorkDoneProgressCreate)
|
||||
env.E.Client().OnProgress(env.onProgress)
|
||||
return env
|
||||
}
|
||||
|
||||
|
|
@ -423,6 +444,38 @@ func (e *Env) onLogMessage(_ context.Context, m *protocol.LogMessageParams) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *Env) onWorkDoneProgressCreate(_ context.Context, m *protocol.WorkDoneProgressCreateParams) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
// panic if we don't have a string token.
|
||||
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 {
|
||||
panic(fmt.Sprintf("got progress report for unknown report %s", token))
|
||||
}
|
||||
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":
|
||||
delete(e.state.outstandingWork, token)
|
||||
}
|
||||
e.checkConditionsLocked()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Env) checkConditionsLocked() {
|
||||
for id, condition := range e.waiters {
|
||||
if v, _, _ := checkExpectations(e.state, condition.expectations); v != Unmet {
|
||||
|
|
@ -512,6 +565,37 @@ func (v Verdict) String() string {
|
|||
return fmt.Sprintf("unrecognized verdict %d", v)
|
||||
}
|
||||
|
||||
// SimpleExpectation holds an arbitrary check func, and implements the Expectation interface.
|
||||
type SimpleExpectation struct {
|
||||
check func(State) Verdict
|
||||
description string
|
||||
}
|
||||
|
||||
// Check invokes e.check.
|
||||
func (e SimpleExpectation) Check(s State) Verdict {
|
||||
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 {
|
||||
check := func(s State) Verdict {
|
||||
if len(s.outstandingWork) == 0 {
|
||||
return Met
|
||||
}
|
||||
return Unmet
|
||||
}
|
||||
return SimpleExpectation{
|
||||
check: check,
|
||||
description: "no outstanding work",
|
||||
}
|
||||
}
|
||||
|
||||
// LogExpectation is an expectation on the log messages received by the editor
|
||||
// from gopls.
|
||||
type LogExpectation struct {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
// 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"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
)
|
||||
|
||||
func TestProgressUpdating(t *testing.T) {
|
||||
e := &Env{
|
||||
state: State{
|
||||
outstandingWork: make(map[string]*workProgress),
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := e.onWorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{
|
||||
Token: "foo",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := e.onWorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{
|
||||
Token: "bar",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updates := []struct {
|
||||
token string
|
||||
value interface{}
|
||||
}{
|
||||
{"foo", protocol.WorkDoneProgressBegin{Kind: "begin", Title: "foo work"}},
|
||||
{"bar", protocol.WorkDoneProgressBegin{Kind: "begin", Title: "bar work"}},
|
||||
{"foo", protocol.WorkDoneProgressEnd{Kind: "end"}},
|
||||
{"bar", protocol.WorkDoneProgressReport{Kind: "report", Percentage: 42}},
|
||||
}
|
||||
for _, update := range updates {
|
||||
params := &protocol.ProgressParams{
|
||||
Token: update.token,
|
||||
Value: update.value,
|
||||
}
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var unmarshaled protocol.ProgressParams
|
||||
if err := json.Unmarshal(data, &unmarshaled); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := e.onProgress(ctx, &unmarshaled); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, ok := e.state.outstandingWork["foo"]; ok {
|
||||
t.Error("got work entry for \"foo\", want none")
|
||||
}
|
||||
got := *e.state.outstandingWork["bar"]
|
||||
want := workProgress{title: "bar work", percent: 42}
|
||||
if got != want {
|
||||
t.Errorf("work progress for \"bar\": %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue