golang.org/x/tools/gopls@v0.15.3/internal/test/integration/env.go (about) 1 // 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 5 package integration 6 7 import ( 8 "context" 9 "fmt" 10 "strings" 11 "sync" 12 "testing" 13 14 "golang.org/x/tools/gopls/internal/protocol" 15 "golang.org/x/tools/gopls/internal/test/integration/fake" 16 "golang.org/x/tools/internal/jsonrpc2/servertest" 17 ) 18 19 // 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. 22 type Env struct { 23 T testing.TB // TODO(rfindley): rename to TB 24 Ctx context.Context 25 26 // Most tests should not need to access the scratch area, editor, server, or 27 // connection, but they are available if needed. 28 Sandbox *fake.Sandbox 29 Server servertest.Connector 30 31 // 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. 44 type Awaiter struct { 45 workdir *fake.Workdir 46 47 mu sync.Mutex 48 // For simplicity, each waiter gets a unique ID. 49 nextWaiterID int 50 state State 51 waiters map[int]*condition 52 } 53 54 func NewAwaiter(workdir *fake.Workdir) *Awaiter { 55 return &Awaiter{ 56 workdir: workdir, 57 state: State{ 58 diagnostics: make(map[string]*protocol.PublishDiagnosticsParams), 59 work: make(map[protocol.ProgressToken]*workProgress), 60 }, 61 waiters: make(map[int]*condition), 62 } 63 } 64 65 // Hooks returns LSP client hooks required for awaiting asynchronous expectations. 66 func (a *Awaiter) Hooks() fake.ClientHooks { 67 return fake.ClientHooks{ 68 OnDiagnostics: a.onDiagnostics, 69 OnLogMessage: a.onLogMessage, 70 OnWorkDoneProgressCreate: a.onWorkDoneProgressCreate, 71 OnProgress: a.onProgress, 72 OnShowDocument: a.onShowDocument, 73 OnShowMessage: a.onShowMessage, 74 OnShowMessageRequest: a.onShowMessageRequest, 75 OnRegisterCapability: a.onRegisterCapability, 76 OnUnregisterCapability: a.onUnregisterCapability, 77 OnApplyEdit: a.onApplyEdit, 78 } 79 } 80 81 // State encapsulates the server state TODO: explain more 82 type State struct { 83 // diagnostics are a map of relative path->diagnostics params 84 diagnostics map[string]*protocol.PublishDiagnosticsParams 85 logs []*protocol.LogMessageParams 86 showDocument []*protocol.ShowDocumentParams 87 showMessage []*protocol.ShowMessageParams 88 showMessageRequest []*protocol.ShowMessageRequestParams 89 90 registrations []*protocol.RegistrationParams 91 registeredCapabilities map[string]protocol.Registration 92 unregistrations []*protocol.UnregistrationParams 93 documentChanges []protocol.DocumentChanges // collected from ApplyEdit downcalls 94 95 // outstandingWork is a map of token->work summary. All tokens are assumed to 96 // be string, though the spec allows for numeric tokens as well. When work 97 // completes, it is deleted from this map. 98 work map[protocol.ProgressToken]*workProgress 99 } 100 101 // completedWork counts complete work items by title. 102 func (s State) completedWork() map[string]uint64 { 103 completed := make(map[string]uint64) 104 for _, work := range s.work { 105 if work.complete { 106 completed[work.title]++ 107 } 108 } 109 return completed 110 } 111 112 // startedWork counts started (and possibly complete) work items. 113 func (s State) startedWork() map[string]uint64 { 114 started := make(map[string]uint64) 115 for _, work := range s.work { 116 started[work.title]++ 117 } 118 return started 119 } 120 121 type workProgress struct { 122 title, msg, endMsg string 123 percent float64 124 complete bool // seen 'end'. 125 } 126 127 // This method, provided for debugging, accesses mutable fields without a lock, 128 // so it must not be called concurrent with any State mutation. 129 func (s State) String() string { 130 var b strings.Builder 131 b.WriteString("#### log messages (see RPC logs for full text):\n") 132 for _, msg := range s.logs { 133 summary := fmt.Sprintf("%v: %q", msg.Type, msg.Message) 134 if len(summary) > 60 { 135 summary = summary[:57] + "..." 136 } 137 // Some logs are quite long, and since they should be reproduced in the RPC 138 // logs on any failure we include here just a short summary. 139 fmt.Fprint(&b, "\t"+summary+"\n") 140 } 141 b.WriteString("\n") 142 b.WriteString("#### diagnostics:\n") 143 for name, params := range s.diagnostics { 144 fmt.Fprintf(&b, "\t%s (version %d):\n", name, params.Version) 145 for _, d := range params.Diagnostics { 146 fmt.Fprintf(&b, "\t\t%d:%d [%s]: %s\n", d.Range.Start.Line, d.Range.Start.Character, d.Source, d.Message) 147 } 148 } 149 b.WriteString("\n") 150 b.WriteString("#### outstanding work:\n") 151 for token, state := range s.work { 152 if state.complete { 153 continue 154 } 155 name := state.title 156 if name == "" { 157 name = fmt.Sprintf("!NO NAME(token: %s)", token) 158 } 159 fmt.Fprintf(&b, "\t%s: %.2f\n", name, state.percent) 160 } 161 b.WriteString("#### completed work:\n") 162 for name, count := range s.completedWork() { 163 fmt.Fprintf(&b, "\t%s: %d\n", name, count) 164 } 165 return b.String() 166 } 167 168 // A condition is satisfied when all expectations are simultaneously 169 // met. At that point, the 'met' channel is closed. On any failure, err is set 170 // and the failed channel is closed. 171 type condition struct { 172 expectations []Expectation 173 verdict chan Verdict 174 } 175 176 func (a *Awaiter) onApplyEdit(_ context.Context, params *protocol.ApplyWorkspaceEditParams) error { 177 a.mu.Lock() 178 defer a.mu.Unlock() 179 180 a.state.documentChanges = append(a.state.documentChanges, params.Edit.DocumentChanges...) 181 a.checkConditionsLocked() 182 return nil 183 } 184 185 func (a *Awaiter) onDiagnostics(_ context.Context, d *protocol.PublishDiagnosticsParams) error { 186 a.mu.Lock() 187 defer a.mu.Unlock() 188 189 pth := a.workdir.URIToPath(d.URI) 190 a.state.diagnostics[pth] = d 191 a.checkConditionsLocked() 192 return nil 193 } 194 195 func (a *Awaiter) onShowDocument(_ context.Context, params *protocol.ShowDocumentParams) error { 196 a.mu.Lock() 197 defer a.mu.Unlock() 198 199 a.state.showDocument = append(a.state.showDocument, params) 200 a.checkConditionsLocked() 201 return nil 202 } 203 204 func (a *Awaiter) onShowMessage(_ context.Context, m *protocol.ShowMessageParams) error { 205 a.mu.Lock() 206 defer a.mu.Unlock() 207 208 a.state.showMessage = append(a.state.showMessage, m) 209 a.checkConditionsLocked() 210 return nil 211 } 212 213 func (a *Awaiter) onShowMessageRequest(_ context.Context, m *protocol.ShowMessageRequestParams) error { 214 a.mu.Lock() 215 defer a.mu.Unlock() 216 217 a.state.showMessageRequest = append(a.state.showMessageRequest, m) 218 a.checkConditionsLocked() 219 return nil 220 } 221 222 func (a *Awaiter) onLogMessage(_ context.Context, m *protocol.LogMessageParams) error { 223 a.mu.Lock() 224 defer a.mu.Unlock() 225 226 a.state.logs = append(a.state.logs, m) 227 a.checkConditionsLocked() 228 return nil 229 } 230 231 func (a *Awaiter) onWorkDoneProgressCreate(_ context.Context, m *protocol.WorkDoneProgressCreateParams) error { 232 a.mu.Lock() 233 defer a.mu.Unlock() 234 235 a.state.work[m.Token] = &workProgress{} 236 return nil 237 } 238 239 func (a *Awaiter) onProgress(_ context.Context, m *protocol.ProgressParams) error { 240 a.mu.Lock() 241 defer a.mu.Unlock() 242 work, ok := a.state.work[m.Token] 243 if !ok { 244 panic(fmt.Sprintf("got progress report for unknown report %v: %v", m.Token, m)) 245 } 246 v := m.Value.(map[string]interface{}) 247 switch kind := v["kind"]; kind { 248 case "begin": 249 work.title = v["title"].(string) 250 if msg, ok := v["message"]; ok { 251 work.msg = msg.(string) 252 } 253 case "report": 254 if pct, ok := v["percentage"]; ok { 255 work.percent = pct.(float64) 256 } 257 if msg, ok := v["message"]; ok { 258 work.msg = msg.(string) 259 } 260 case "end": 261 work.complete = true 262 if msg, ok := v["message"]; ok { 263 work.endMsg = msg.(string) 264 } 265 } 266 a.checkConditionsLocked() 267 return nil 268 } 269 270 func (a *Awaiter) onRegisterCapability(_ context.Context, m *protocol.RegistrationParams) error { 271 a.mu.Lock() 272 defer a.mu.Unlock() 273 274 a.state.registrations = append(a.state.registrations, m) 275 if a.state.registeredCapabilities == nil { 276 a.state.registeredCapabilities = make(map[string]protocol.Registration) 277 } 278 for _, reg := range m.Registrations { 279 a.state.registeredCapabilities[reg.Method] = reg 280 } 281 a.checkConditionsLocked() 282 return nil 283 } 284 285 func (a *Awaiter) onUnregisterCapability(_ context.Context, m *protocol.UnregistrationParams) error { 286 a.mu.Lock() 287 defer a.mu.Unlock() 288 289 a.state.unregistrations = append(a.state.unregistrations, m) 290 a.checkConditionsLocked() 291 return nil 292 } 293 294 func (a *Awaiter) checkConditionsLocked() { 295 for id, condition := range a.waiters { 296 if v, _ := checkExpectations(a.state, condition.expectations); v != Unmet { 297 delete(a.waiters, id) 298 condition.verdict <- v 299 } 300 } 301 } 302 303 // TakeDocumentChanges returns any accumulated document changes (from 304 // server ApplyEdit RPC downcalls) and resets the list. 305 func (a *Awaiter) TakeDocumentChanges() []protocol.DocumentChanges { 306 a.mu.Lock() 307 defer a.mu.Unlock() 308 309 res := a.state.documentChanges 310 a.state.documentChanges = nil 311 return res 312 } 313 314 // checkExpectations reports whether s meets all expectations. 315 func checkExpectations(s State, expectations []Expectation) (Verdict, string) { 316 finalVerdict := Met 317 var summary strings.Builder 318 for _, e := range expectations { 319 v := e.Check(s) 320 if v > finalVerdict { 321 finalVerdict = v 322 } 323 fmt.Fprintf(&summary, "%v: %s\n", v, e.Description) 324 } 325 return finalVerdict, summary.String() 326 } 327 328 // Await blocks until the given expectations are all simultaneously met. 329 // 330 // Generally speaking Await should be avoided because it blocks indefinitely if 331 // gopls ends up in a state where the expectations are never going to be met. 332 // Use AfterChange or OnceMet instead, so that the runner knows when to stop 333 // waiting. 334 func (e *Env) Await(expectations ...Expectation) { 335 e.T.Helper() 336 if err := e.Awaiter.Await(e.Ctx, expectations...); err != nil { 337 e.T.Fatal(err) 338 } 339 } 340 341 // OnceMet blocks until the precondition is met by the state or becomes 342 // unmeetable. If it was met, OnceMet checks that the state meets all 343 // expectations in mustMeets. 344 func (e *Env) OnceMet(precondition Expectation, mustMeets ...Expectation) { 345 e.T.Helper() 346 e.Await(OnceMet(precondition, mustMeets...)) 347 } 348 349 // Await waits for all expectations to simultaneously be met. It should only be 350 // called from the main test goroutine. 351 func (a *Awaiter) Await(ctx context.Context, expectations ...Expectation) error { 352 a.mu.Lock() 353 // Before adding the waiter, we check if the condition is currently met or 354 // failed to avoid a race where the condition was realized before Await was 355 // called. 356 switch verdict, summary := checkExpectations(a.state, expectations); verdict { 357 case Met: 358 a.mu.Unlock() 359 return nil 360 case Unmeetable: 361 err := fmt.Errorf("unmeetable expectations:\n%s\nstate:\n%v", summary, a.state) 362 a.mu.Unlock() 363 return err 364 } 365 cond := &condition{ 366 expectations: expectations, 367 verdict: make(chan Verdict), 368 } 369 a.waiters[a.nextWaiterID] = cond 370 a.nextWaiterID++ 371 a.mu.Unlock() 372 373 var err error 374 select { 375 case <-ctx.Done(): 376 err = ctx.Err() 377 case v := <-cond.verdict: 378 if v != Met { 379 err = fmt.Errorf("condition has final verdict %v", v) 380 } 381 } 382 a.mu.Lock() 383 defer a.mu.Unlock() 384 _, summary := checkExpectations(a.state, expectations) 385 386 // Debugging an unmet expectation can be tricky, so we put some effort into 387 // nicely formatting the failure. 388 if err != nil { 389 return fmt.Errorf("waiting on:\n%s\nerr:%v\n\nstate:\n%v", summary, err, a.state) 390 } 391 return nil 392 }