blob: 77744ae614a4eb278ad5ba87a5ccc6ded8a01fe4 [file] [log] [blame]
Rob Findley0fd2d642020-02-06 19:50:37 -05001// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Rob Findley0fd2d642020-02-06 19:50:37 -05005package regtest
6
7import (
8 "context"
9 "fmt"
10 "strings"
11 "sync"
12 "testing"
Rob Findley0fd2d642020-02-06 19:50:37 -050013
14 "golang.org/x/tools/internal/jsonrpc2/servertest"
Rob Findley0fd2d642020-02-06 19:50:37 -050015 "golang.org/x/tools/internal/lsp/fake"
Rob Findley0fd2d642020-02-06 19:50:37 -050016 "golang.org/x/tools/internal/lsp/protocol"
17)
18
Robert Findley87f47bb2022-07-22 15:38:01 -040019// Env holds the building blocks of an editor testing environment, providing
20// wrapper methods that hide the boilerplate of plumbing contexts and checking
21// errors.
Rob Findley0fd2d642020-02-06 19:50:37 -050022type Env struct {
Robert Findley87f47bb2022-07-22 15:38:01 -040023 T testing.TB // TODO(rfindley): rename to TB
Rob Findley1fc30e12020-03-23 17:26:05 -040024 Ctx context.Context
Rob Findley0fd2d642020-02-06 19:50:37 -050025
Rob Findleyc20a87c2020-04-29 00:00:52 -040026 // Most tests should not need to access the scratch area, editor, server, or
Rob Findleyde023d52020-03-04 13:29:23 -050027 // connection, but they are available if needed.
Rob Findleyc20a87c2020-04-29 00:00:52 -040028 Sandbox *fake.Sandbox
Rob Findleyc20a87c2020-04-29 00:00:52 -040029 Server servertest.Connector
Rob Findley0fd2d642020-02-06 19:50:37 -050030
Robert Findley87f47bb2022-07-22 15:38:01 -040031 // Editor is owned by the Env, and shut down
32 Editor *fake.Editor
33
34 Awaiter *Awaiter
35}
36
37// An Awaiter keeps track of relevant LSP state, so that it may be asserted
38// upon with Expectations.
39//
40// Wire it into a fake.Editor using Awaiter.Hooks().
41//
42// TODO(rfindley): consider simply merging Awaiter with the fake.Editor. It
43// probably is not worth its own abstraction.
44type Awaiter struct {
45 workdir *fake.Workdir
46
Rob Findley0fd2d642020-02-06 19:50:37 -050047 mu sync.Mutex
48 // For simplicity, each waiter gets a unique ID.
Rob Findleyf0387852020-04-15 17:14:53 -040049 nextWaiterID int
50 state State
51 waiters map[int]*condition
Rob Findley0fd2d642020-02-06 19:50:37 -050052}
53
Robert Findley87f47bb2022-07-22 15:38:01 -040054func NewAwaiter(workdir *fake.Workdir) *Awaiter {
55 return &Awaiter{
56 workdir: workdir,
57 state: State{
58 diagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
59 outstandingWork: make(map[protocol.ProgressToken]*workProgress),
60 startedWork: make(map[string]uint64),
61 completedWork: make(map[string]uint64),
62 },
63 waiters: make(map[int]*condition),
64 }
65}
66
67func (a *Awaiter) Hooks() fake.ClientHooks {
68 return fake.ClientHooks{
69 OnDiagnostics: a.onDiagnostics,
70 OnLogMessage: a.onLogMessage,
71 OnWorkDoneProgressCreate: a.onWorkDoneProgressCreate,
72 OnProgress: a.onProgress,
73 OnShowMessage: a.onShowMessage,
74 OnShowMessageRequest: a.onShowMessageRequest,
75 OnRegistration: a.onRegistration,
76 OnUnregistration: a.onUnregistration,
77 }
78}
79
Rob Findleyf0387852020-04-15 17:14:53 -040080// State encapsulates the server state TODO: explain more
81type State struct {
82 // diagnostics are a map of relative path->diagnostics params
Rebecca Stambler4d5ea462020-05-28 21:21:29 -040083 diagnostics map[string]*protocol.PublishDiagnosticsParams
84 logs []*protocol.LogMessageParams
85 showMessage []*protocol.ShowMessageParams
86 showMessageRequest []*protocol.ShowMessageRequestParams
Rebecca Stambler383b97c2020-07-28 18:18:43 -040087
Robert Findley92d58ea2022-08-05 18:08:11 -040088 registrations []*protocol.RegistrationParams
89 registeredCapabilities map[string]protocol.Registration
90 unregistrations []*protocol.UnregistrationParams
Rebecca Stambler383b97c2020-07-28 18:18:43 -040091
Rob Findley38a97e02020-04-21 23:44:31 -040092 // outstandingWork is a map of token->work summary. All tokens are assumed to
93 // be string, though the spec allows for numeric tokens as well. When work
94 // completes, it is deleted from this map.
Rob Findley30a08932020-08-06 23:29:21 -040095 outstandingWork map[protocol.ProgressToken]*workProgress
Rob Findleyf4a41292021-05-05 15:09:54 -040096 startedWork map[string]uint64
Rob Findley917f61d2021-01-20 12:27:51 -050097 completedWork map[string]uint64
Rob Findley38a97e02020-04-21 23:44:31 -040098}
99
100type workProgress struct {
Rebecca Stambler74f29862020-11-10 00:13:20 -0500101 title, msg string
102 percent float64
Rob Findleyf0387852020-04-15 17:14:53 -0400103}
104
105func (s State) String() string {
106 var b strings.Builder
107 b.WriteString("#### log messages (see RPC logs for full text):\n")
108 for _, msg := range s.logs {
109 summary := fmt.Sprintf("%v: %q", msg.Type, msg.Message)
110 if len(summary) > 60 {
111 summary = summary[:57] + "..."
112 }
113 // Some logs are quite long, and since they should be reproduced in the RPC
114 // logs on any failure we include here just a short summary.
115 fmt.Fprint(&b, "\t"+summary+"\n")
116 }
117 b.WriteString("\n")
118 b.WriteString("#### diagnostics:\n")
119 for name, params := range s.diagnostics {
120 fmt.Fprintf(&b, "\t%s (version %d):\n", name, int(params.Version))
121 for _, d := range params.Diagnostics {
122 fmt.Fprintf(&b, "\t\t(%d, %d): %s\n", int(d.Range.Start.Line), int(d.Range.Start.Character), d.Message)
123 }
124 }
Rob Findley38a97e02020-04-21 23:44:31 -0400125 b.WriteString("\n")
126 b.WriteString("#### outstanding work:\n")
127 for token, state := range s.outstandingWork {
128 name := state.title
129 if name == "" {
130 name = fmt.Sprintf("!NO NAME(token: %s)", token)
131 }
Rob Findleya45abac2020-06-02 18:24:26 -0400132 fmt.Fprintf(&b, "\t%s: %.2f\n", name, state.percent)
133 }
134 b.WriteString("#### completed work:\n")
135 for name, count := range s.completedWork {
136 fmt.Fprintf(&b, "\t%s: %d\n", name, count)
Rob Findley38a97e02020-04-21 23:44:31 -0400137 }
Rob Findleyf0387852020-04-15 17:14:53 -0400138 return b.String()
139}
140
141// A condition is satisfied when all expectations are simultaneously
Rob Findley8f5be0d2020-04-01 14:56:48 -0400142// met. At that point, the 'met' channel is closed. On any failure, err is set
143// and the failed channel is closed.
Rob Findleyf0387852020-04-15 17:14:53 -0400144type condition struct {
145 expectations []Expectation
146 verdict chan Verdict
Rob Findley0fd2d642020-02-06 19:50:37 -0500147}
148
Robert Findley87f47bb2022-07-22 15:38:01 -0400149func (a *Awaiter) onDiagnostics(_ context.Context, d *protocol.PublishDiagnosticsParams) error {
150 a.mu.Lock()
151 defer a.mu.Unlock()
Rob Findley697795d2021-08-08 20:12:22 -0400152
Robert Findley87f47bb2022-07-22 15:38:01 -0400153 pth := a.workdir.URIToPath(d.URI)
154 a.state.diagnostics[pth] = d
155 a.checkConditionsLocked()
Rob Findley0fd2d642020-02-06 19:50:37 -0500156 return nil
157}
158
Robert Findley87f47bb2022-07-22 15:38:01 -0400159func (a *Awaiter) onShowMessage(_ context.Context, m *protocol.ShowMessageParams) error {
160 a.mu.Lock()
161 defer a.mu.Unlock()
pjwed308ab2020-04-29 13:33:43 -0400162
Robert Findley87f47bb2022-07-22 15:38:01 -0400163 a.state.showMessage = append(a.state.showMessage, m)
164 a.checkConditionsLocked()
pjwed308ab2020-04-29 13:33:43 -0400165 return nil
166}
167
Robert Findley87f47bb2022-07-22 15:38:01 -0400168func (a *Awaiter) onShowMessageRequest(_ context.Context, m *protocol.ShowMessageRequestParams) error {
169 a.mu.Lock()
170 defer a.mu.Unlock()
Rebecca Stambler4d5ea462020-05-28 21:21:29 -0400171
Robert Findley87f47bb2022-07-22 15:38:01 -0400172 a.state.showMessageRequest = append(a.state.showMessageRequest, m)
173 a.checkConditionsLocked()
Rebecca Stambler4d5ea462020-05-28 21:21:29 -0400174 return nil
175}
176
Robert Findley87f47bb2022-07-22 15:38:01 -0400177func (a *Awaiter) onLogMessage(_ context.Context, m *protocol.LogMessageParams) error {
178 a.mu.Lock()
179 defer a.mu.Unlock()
Rob Findley46dc3322020-04-22 17:54:30 -0400180
Robert Findley87f47bb2022-07-22 15:38:01 -0400181 a.state.logs = append(a.state.logs, m)
182 a.checkConditionsLocked()
Rob Findleyf0387852020-04-15 17:14:53 -0400183 return nil
184}
185
Robert Findley87f47bb2022-07-22 15:38:01 -0400186func (a *Awaiter) onWorkDoneProgressCreate(_ context.Context, m *protocol.WorkDoneProgressCreateParams) error {
187 a.mu.Lock()
188 defer a.mu.Unlock()
Rob Findley46dc3322020-04-22 17:54:30 -0400189
Robert Findley87f47bb2022-07-22 15:38:01 -0400190 a.state.outstandingWork[m.Token] = &workProgress{}
Rob Findley38a97e02020-04-21 23:44:31 -0400191 return nil
192}
193
Robert Findley87f47bb2022-07-22 15:38:01 -0400194func (a *Awaiter) onProgress(_ context.Context, m *protocol.ProgressParams) error {
195 a.mu.Lock()
196 defer a.mu.Unlock()
197 work, ok := a.state.outstandingWork[m.Token]
Rob Findley38a97e02020-04-21 23:44:31 -0400198 if !ok {
Rob Findley30a08932020-08-06 23:29:21 -0400199 panic(fmt.Sprintf("got progress report for unknown report %v: %v", m.Token, m))
Rob Findley38a97e02020-04-21 23:44:31 -0400200 }
201 v := m.Value.(map[string]interface{})
202 switch kind := v["kind"]; kind {
203 case "begin":
204 work.title = v["title"].(string)
Robert Findley87f47bb2022-07-22 15:38:01 -0400205 a.state.startedWork[work.title] = a.state.startedWork[work.title] + 1
Rebecca Stambler74f29862020-11-10 00:13:20 -0500206 if msg, ok := v["message"]; ok {
207 work.msg = msg.(string)
208 }
Rob Findley38a97e02020-04-21 23:44:31 -0400209 case "report":
210 if pct, ok := v["percentage"]; ok {
211 work.percent = pct.(float64)
212 }
Rebecca Stambler74f29862020-11-10 00:13:20 -0500213 if msg, ok := v["message"]; ok {
214 work.msg = msg.(string)
215 }
Rob Findley38a97e02020-04-21 23:44:31 -0400216 case "end":
Robert Findley87f47bb2022-07-22 15:38:01 -0400217 title := a.state.outstandingWork[m.Token].title
218 a.state.completedWork[title] = a.state.completedWork[title] + 1
219 delete(a.state.outstandingWork, m.Token)
Rob Findley38a97e02020-04-21 23:44:31 -0400220 }
Robert Findley87f47bb2022-07-22 15:38:01 -0400221 a.checkConditionsLocked()
Rob Findley38a97e02020-04-21 23:44:31 -0400222 return nil
223}
224
Robert Findley87f47bb2022-07-22 15:38:01 -0400225func (a *Awaiter) onRegistration(_ context.Context, m *protocol.RegistrationParams) error {
226 a.mu.Lock()
227 defer a.mu.Unlock()
Rebecca Stambler383b97c2020-07-28 18:18:43 -0400228
Robert Findley87f47bb2022-07-22 15:38:01 -0400229 a.state.registrations = append(a.state.registrations, m)
Robert Findley92d58ea2022-08-05 18:08:11 -0400230 if a.state.registeredCapabilities == nil {
231 a.state.registeredCapabilities = make(map[string]protocol.Registration)
232 }
233 for _, reg := range m.Registrations {
234 a.state.registeredCapabilities[reg.Method] = reg
235 }
Robert Findley87f47bb2022-07-22 15:38:01 -0400236 a.checkConditionsLocked()
Rebecca Stambler383b97c2020-07-28 18:18:43 -0400237 return nil
238}
239
Robert Findley87f47bb2022-07-22 15:38:01 -0400240func (a *Awaiter) onUnregistration(_ context.Context, m *protocol.UnregistrationParams) error {
241 a.mu.Lock()
242 defer a.mu.Unlock()
Rebecca Stambler383b97c2020-07-28 18:18:43 -0400243
Robert Findley87f47bb2022-07-22 15:38:01 -0400244 a.state.unregistrations = append(a.state.unregistrations, m)
245 a.checkConditionsLocked()
Rebecca Stambler383b97c2020-07-28 18:18:43 -0400246 return nil
247}
248
Robert Findley87f47bb2022-07-22 15:38:01 -0400249func (a *Awaiter) checkConditionsLocked() {
250 for id, condition := range a.waiters {
251 if v, _ := checkExpectations(a.state, condition.expectations); v != Unmet {
252 delete(a.waiters, id)
Rob Findleyf0387852020-04-15 17:14:53 -0400253 condition.verdict <- v
254 }
255 }
256}
257
Rob Findleyf0387852020-04-15 17:14:53 -0400258// checkExpectations reports whether s meets all expectations.
Rob Findleyc8d9e052020-09-15 23:11:52 -0400259func checkExpectations(s State, expectations []Expectation) (Verdict, string) {
Rob Findleyf0387852020-04-15 17:14:53 -0400260 finalVerdict := Met
261 var summary strings.Builder
Rob Findley0fd2d642020-02-06 19:50:37 -0500262 for _, e := range expectations {
Rob Findleyc8d9e052020-09-15 23:11:52 -0400263 v := e.Check(s)
Rob Findleyf0387852020-04-15 17:14:53 -0400264 if v > finalVerdict {
265 finalVerdict = v
Rob Findley8f5be0d2020-04-01 14:56:48 -0400266 }
Robert Findley090b14e2022-05-13 17:39:10 -0400267 summary.WriteString(fmt.Sprintf("%v: %s\n", v, e.Description()))
Rob Findley0fd2d642020-02-06 19:50:37 -0500268 }
Rob Findleyc8d9e052020-09-15 23:11:52 -0400269 return finalVerdict, summary.String()
Rob Findleyf0387852020-04-15 17:14:53 -0400270}
271
pjw308beac2020-06-06 07:47:14 -0400272// DiagnosticsFor returns the current diagnostics for the file. It is useful
273// after waiting on AnyDiagnosticAtCurrentVersion, when the desired diagnostic
274// is not simply described by DiagnosticAt.
Robert Findley87f47bb2022-07-22 15:38:01 -0400275//
276// TODO(rfindley): this method is inherently racy. Replace usages of this
277// method with the atomic OnceMet(..., ReadDiagnostics) pattern.
278func (a *Awaiter) DiagnosticsFor(name string) *protocol.PublishDiagnosticsParams {
279 a.mu.Lock()
280 defer a.mu.Unlock()
281 return a.state.diagnostics[name]
282}
283
284func (e *Env) Await(expectations ...Expectation) {
Robert Findley96d05aa2022-08-08 16:36:42 -0400285 e.T.Helper()
Robert Findley87f47bb2022-07-22 15:38:01 -0400286 if err := e.Awaiter.Await(e.Ctx, expectations...); err != nil {
287 e.T.Fatal(err)
288 }
pjw308beac2020-06-06 07:47:14 -0400289}
290
Rob Findleyf0387852020-04-15 17:14:53 -0400291// Await waits for all expectations to simultaneously be met. It should only be
292// called from the main test goroutine.
Robert Findley87f47bb2022-07-22 15:38:01 -0400293func (a *Awaiter) Await(ctx context.Context, expectations ...Expectation) error {
294 a.mu.Lock()
Rob Findley8f5be0d2020-04-01 14:56:48 -0400295 // Before adding the waiter, we check if the condition is currently met or
296 // failed to avoid a race where the condition was realized before Await was
297 // called.
Robert Findley87f47bb2022-07-22 15:38:01 -0400298 switch verdict, summary := checkExpectations(a.state, expectations); verdict {
Rob Findleyf0387852020-04-15 17:14:53 -0400299 case Met:
Robert Findley87f47bb2022-07-22 15:38:01 -0400300 a.mu.Unlock()
301 return nil
Rob Findleyf0387852020-04-15 17:14:53 -0400302 case Unmeetable:
Robert Findley87f47bb2022-07-22 15:38:01 -0400303 err := fmt.Errorf("unmeetable expectations:\n%s\nstate:\n%v", summary, a.state)
304 a.mu.Unlock()
305 return err
Rob Findley0fd2d642020-02-06 19:50:37 -0500306 }
Rob Findleyf0387852020-04-15 17:14:53 -0400307 cond := &condition{
Rob Findley0fd2d642020-02-06 19:50:37 -0500308 expectations: expectations,
Rob Findleyf0387852020-04-15 17:14:53 -0400309 verdict: make(chan Verdict),
Rob Findley0fd2d642020-02-06 19:50:37 -0500310 }
Robert Findley87f47bb2022-07-22 15:38:01 -0400311 a.waiters[a.nextWaiterID] = cond
312 a.nextWaiterID++
313 a.mu.Unlock()
Rob Findley0fd2d642020-02-06 19:50:37 -0500314
Rob Findleyf0387852020-04-15 17:14:53 -0400315 var err error
Rob Findley0fd2d642020-02-06 19:50:37 -0500316 select {
Robert Findley87f47bb2022-07-22 15:38:01 -0400317 case <-ctx.Done():
318 err = ctx.Err()
Rob Findleyf0387852020-04-15 17:14:53 -0400319 case v := <-cond.verdict:
320 if v != Met {
321 err = fmt.Errorf("condition has final verdict %v", v)
Rob Findley0fd2d642020-02-06 19:50:37 -0500322 }
323 }
Robert Findley87f47bb2022-07-22 15:38:01 -0400324 a.mu.Lock()
325 defer a.mu.Unlock()
326 _, summary := checkExpectations(a.state, expectations)
Rob Findleyf0387852020-04-15 17:14:53 -0400327
Rebecca Stambler504fe872020-04-01 21:31:43 -0400328 // Debugging an unmet expectation can be tricky, so we put some effort into
329 // nicely formatting the failure.
Rob Findleyf0387852020-04-15 17:14:53 -0400330 if err != nil {
Robert Findley87f47bb2022-07-22 15:38:01 -0400331 return fmt.Errorf("waiting on:\n%s\nerr:%v\n\nstate:\n%v", summary, err, a.state)
Rob Findleyf0387852020-04-15 17:14:53 -0400332 }
Robert Findley87f47bb2022-07-22 15:38:01 -0400333 return nil
Rob Findley0fd2d642020-02-06 19:50:37 -0500334}