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.
12 "golang.org/x/tools/internal/lsp"
13 "golang.org/x/tools/internal/lsp/protocol"
14 "golang.org/x/tools/internal/span"
17 // An Expectation asserts that the state of the editor at a point in time
18 // matches an expected condition. This is used for signaling in tests when
19 // certain conditions in the editor are met.
20 type Expectation interface {
21 // Check determines whether the state of the editor satisfies the
22 // expectation, returning the results that met the condition.
24 // Description is a human-readable description of the expectation.
29 // InitialWorkspaceLoad is an expectation that the workspace initial load has
30 // completed. It is verified via workdone reporting.
31 InitialWorkspaceLoad = CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1)
34 // A Verdict is the result of checking an expectation against the current
38 // Order matters for the following constants: verdicts are sorted in order of
41 // Met indicates that an expectation is satisfied by the current state.
43 // Unmet indicates that an expectation is not currently met, but could be met
46 // Unmeetable indicates that an expectation cannot be satisfied in the
51 func (v Verdict) String() string {
60 return fmt.Sprintf("unrecognized verdict %d", v)
63 // SimpleExpectation holds an arbitrary check func, and implements the Expectation interface.
64 type SimpleExpectation struct {
65 check func(State) Verdict
69 // Check invokes e.check.
70 func (e SimpleExpectation) Check(s State) Verdict {
74 // Description returns e.descriptin.
75 func (e SimpleExpectation) Description() string {
79 // OnceMet returns an Expectation that, once the precondition is met, asserts
80 // that mustMeet is met.
81 func OnceMet(precondition Expectation, mustMeet Expectation) *SimpleExpectation {
82 check := func(s State) Verdict {
83 switch pre := precondition.Check(s); pre {
87 verdict := mustMeet.Check(s)
96 return &SimpleExpectation{
98 description: fmt.Sprintf("once %q is met, must have %q", precondition.Description(), mustMeet.Description()),
102 // ReadDiagnostics is an 'expectation' that is used to read diagnostics
103 // atomically. It is intended to be used with 'OnceMet'.
104 func ReadDiagnostics(fileName string, into *protocol.PublishDiagnosticsParams) *SimpleExpectation {
105 check := func(s State) Verdict {
106 diags, ok := s.diagnostics[fileName]
113 return &SimpleExpectation{
115 description: fmt.Sprintf("read diagnostics for %q", fileName),
119 // NoOutstandingWork asserts that there is no work initiated using the LSP
120 // $/progress API that has not completed.
121 func NoOutstandingWork() SimpleExpectation {
122 check := func(s State) Verdict {
123 if len(s.outstandingWork) == 0 {
128 return SimpleExpectation{
130 description: "no outstanding work",
134 // NoShowMessage asserts that the editor has not received a ShowMessage.
135 func NoShowMessage() SimpleExpectation {
136 check := func(s State) Verdict {
137 if len(s.showMessage) == 0 {
142 return SimpleExpectation{
144 description: "no ShowMessage received",
148 // ShownMessage asserts that the editor has received a ShownMessage with the
150 func ShownMessage(title string) SimpleExpectation {
151 check := func(s State) Verdict {
152 for _, m := range s.showMessage {
153 if strings.Contains(m.Message, title) {
159 return SimpleExpectation{
161 description: "received ShowMessage",
165 // ShowMessageRequest asserts that the editor has received a ShowMessageRequest
166 // with an action item that has the given title.
167 func ShowMessageRequest(title string) SimpleExpectation {
168 check := func(s State) Verdict {
169 if len(s.showMessageRequest) == 0 {
172 // Only check the most recent one.
173 m := s.showMessageRequest[len(s.showMessageRequest)-1]
174 if len(m.Actions) == 0 || len(m.Actions) > 1 {
177 if m.Actions[0].Title == title {
182 return SimpleExpectation{
184 description: "received ShowMessageRequest",
188 // CompletedWork expects a work item to have been completed >= atLeast times.
190 // Since the Progress API doesn't include any hidden metadata, we must use the
191 // progress notification title to identify the work we expect to be completed.
192 func CompletedWork(title string, atLeast int) SimpleExpectation {
193 check := func(s State) Verdict {
194 if s.completedWork[title] >= atLeast {
199 return SimpleExpectation{
201 description: fmt.Sprintf("completed work %q at least %d time(s)", title, atLeast),
205 // LogExpectation is an expectation on the log messages received by the editor
207 type LogExpectation struct {
208 check func([]*protocol.LogMessageParams) Verdict
212 // Check implements the Expectation interface.
213 func (e LogExpectation) Check(s State) Verdict {
214 return e.check(s.logs)
217 // Description implements the Expectation interface.
218 func (e LogExpectation) Description() string {
222 // NoErrorLogs asserts that the client has not received any log messages of
224 func NoErrorLogs() LogExpectation {
225 return NoLogMatching(protocol.Error, "")
228 // LogMatching asserts that the client has received a log message
229 // of type typ matching the regexp re.
230 func LogMatching(typ protocol.MessageType, re string, count int) LogExpectation {
231 rec, err := regexp.Compile(re)
235 check := func(msgs []*protocol.LogMessageParams) Verdict {
237 for _, msg := range msgs {
238 if msg.Type == typ && rec.Match([]byte(msg.Message)) {
247 return LogExpectation{
249 description: fmt.Sprintf("log message matching %q", re),
253 // NoLogMatching asserts that the client has not received a log message
254 // of type typ matching the regexp re. If re is an empty string, any log
255 // message is considered a match.
256 func NoLogMatching(typ protocol.MessageType, re string) LogExpectation {
260 r, err = regexp.Compile(re)
265 check := func(msgs []*protocol.LogMessageParams) Verdict {
266 for _, msg := range msgs {
270 if r == nil || r.Match([]byte(msg.Message)) {
276 return LogExpectation{
278 description: fmt.Sprintf("no log message matching %q", re),
282 // RegistrationExpectation is an expectation on the capability registrations
283 // received by the editor from gopls.
284 type RegistrationExpectation struct {
285 check func([]*protocol.RegistrationParams) Verdict
289 // Check implements the Expectation interface.
290 func (e RegistrationExpectation) Check(s State) Verdict {
291 return e.check(s.registrations)
294 // Description implements the Expectation interface.
295 func (e RegistrationExpectation) Description() string {
299 // RegistrationMatching asserts that the client has received a capability
300 // registration matching the given regexp.
301 func RegistrationMatching(re string) RegistrationExpectation {
302 rec, err := regexp.Compile(re)
306 check := func(params []*protocol.RegistrationParams) Verdict {
307 for _, p := range params {
308 for _, r := range p.Registrations {
309 if rec.Match([]byte(r.Method)) {
316 return RegistrationExpectation{
318 description: fmt.Sprintf("registration matching %q", re),
322 // UnregistrationExpectation is an expectation on the capability
323 // unregistrations received by the editor from gopls.
324 type UnregistrationExpectation struct {
325 check func([]*protocol.UnregistrationParams) Verdict
329 // Check implements the Expectation interface.
330 func (e UnregistrationExpectation) Check(s State) Verdict {
331 return e.check(s.unregistrations)
334 // Description implements the Expectation interface.
335 func (e UnregistrationExpectation) Description() string {
339 // UnregistrationMatching asserts that the client has received an
340 // unregistration whose ID matches the given regexp.
341 func UnregistrationMatching(re string) UnregistrationExpectation {
342 rec, err := regexp.Compile(re)
346 check := func(params []*protocol.UnregistrationParams) Verdict {
347 for _, p := range params {
348 for _, r := range p.Unregisterations {
349 if rec.Match([]byte(r.Method)) {
356 return UnregistrationExpectation{
358 description: fmt.Sprintf("unregistration matching %q", re),
362 // A DiagnosticExpectation is a condition that must be met by the current set
363 // of diagnostics for a file.
364 type DiagnosticExpectation struct {
365 // IsMet determines whether the diagnostics for this file version satisfy our
367 isMet func(*protocol.PublishDiagnosticsParams) bool
368 // Description is a human-readable description of the diagnostic expectation.
370 // Path is the scratch workdir-relative path to the file being asserted on.
374 // Check implements the Expectation interface.
375 func (e DiagnosticExpectation) Check(s State) Verdict {
376 if diags, ok := s.diagnostics[e.path]; ok && e.isMet(diags) {
382 // Description implements the Expectation interface.
383 func (e DiagnosticExpectation) Description() string {
384 return fmt.Sprintf("%s: %s", e.path, e.description)
387 // EmptyDiagnostics asserts that empty diagnostics are sent for the
388 // workspace-relative path name.
389 func EmptyDiagnostics(name string) Expectation {
390 check := func(s State) Verdict {
391 if diags := s.diagnostics[name]; diags != nil && len(diags.Diagnostics) == 0 {
396 return SimpleExpectation{
398 description: "empty diagnostics",
402 // NoDiagnostics asserts that no diagnostics are sent for the
403 // workspace-relative path name. It should be used primarily in conjunction
404 // with a OnceMet, as it has to check that all outstanding diagnostics have
405 // already been delivered.
406 func NoDiagnostics(name string) Expectation {
407 check := func(s State) Verdict {
408 if _, ok := s.diagnostics[name]; !ok {
413 return SimpleExpectation{
415 description: "no diagnostics",
419 // AnyDiagnosticAtCurrentVersion asserts that there is a diagnostic report for
420 // the current edited version of the buffer corresponding to the given
421 // workdir-relative pathname.
422 func (e *Env) AnyDiagnosticAtCurrentVersion(name string) DiagnosticExpectation {
423 version := e.Editor.BufferVersion(name)
424 isMet := func(diags *protocol.PublishDiagnosticsParams) bool {
425 return int(diags.Version) == version
427 return DiagnosticExpectation{
429 description: fmt.Sprintf("any diagnostics at version %d", version),
434 // DiagnosticAtRegexp expects that there is a diagnostic entry at the start
435 // position matching the regexp search string re in the buffer specified by
436 // name. Note that this currently ignores the end position.
437 func (e *Env) DiagnosticAtRegexp(name, re string) DiagnosticExpectation {
439 pos := e.RegexpSearch(name, re)
440 expectation := DiagnosticAt(name, pos.Line, pos.Column)
441 expectation.description += fmt.Sprintf(" (location of %q)", re)
445 // DiagnosticAt asserts that there is a diagnostic entry at the position
446 // specified by line and col, for the workdir-relative path name.
447 func DiagnosticAt(name string, line, col int) DiagnosticExpectation {
448 isMet := func(diags *protocol.PublishDiagnosticsParams) bool {
449 for _, d := range diags.Diagnostics {
450 if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) {
456 return DiagnosticExpectation{
458 description: fmt.Sprintf("diagnostic at {line:%d, column:%d}", line, col),
463 // NoDiagnosticAtRegexp expects that there is no diagnostic entry at the start
464 // position matching the regexp search string re in the buffer specified by
465 // name. Note that this currently ignores the end position.
466 // This should only be used in combination with OnceMet for a given condition,
467 // otherwise it may always succeed.
468 func (e *Env) NoDiagnosticAtRegexp(name, re string) DiagnosticExpectation {
470 pos := e.RegexpSearch(name, re)
471 expectation := NoDiagnosticAt(name, pos.Line, pos.Column)
472 expectation.description += fmt.Sprintf(" (location of %q)", re)
476 // NoDiagnosticAt asserts that there is no diagnostic entry at the position
477 // specified by line and col, for the workdir-relative path name.
478 // This should only be used in combination with OnceMet for a given condition,
479 // otherwise it may always succeed.
480 func NoDiagnosticAt(name string, line, col int) DiagnosticExpectation {
481 isMet := func(diags *protocol.PublishDiagnosticsParams) bool {
482 for _, d := range diags.Diagnostics {
483 if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) {
489 return DiagnosticExpectation{
491 description: fmt.Sprintf("no diagnostic at {line:%d, column:%d}", line, col),
496 // NoDiagnosticWithMessage asserts that there is no diagnostic entry with the
499 // This should only be used in combination with OnceMet for a given condition,
500 // otherwise it may always succeed.
501 func NoDiagnosticWithMessage(msg string) DiagnosticExpectation {
503 isMet := func(diags *protocol.PublishDiagnosticsParams) bool {
504 for _, d := range diags.Diagnostics {
505 if d.Message == msg {
513 path = uri.Filename()
515 return DiagnosticExpectation{
517 description: fmt.Sprintf("no diagnostic with message %s", msg),