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/fake"
14 "golang.org/x/tools/internal/lsp/protocol"
15 "golang.org/x/tools/internal/testenv"
18 // An Expectation asserts that the state of the editor at a point in time
19 // matches an expected condition. This is used for signaling in tests when
20 // certain conditions in the editor are met.
21 type Expectation interface {
22 // Check determines whether the state of the editor satisfies the
23 // expectation, returning the results that met the condition.
25 // Description is a human-readable description of the expectation.
30 // InitialWorkspaceLoad is an expectation that the workspace initial load has
31 // completed. It is verified via workdone reporting.
32 InitialWorkspaceLoad = CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1)
35 // A Verdict is the result of checking an expectation against the current
39 // Order matters for the following constants: verdicts are sorted in order of
42 // Met indicates that an expectation is satisfied by the current state.
44 // Unmet indicates that an expectation is not currently met, but could be met
47 // Unmeetable indicates that an expectation cannot be satisfied in the
52 func (v Verdict) String() string {
61 return fmt.Sprintf("unrecognized verdict %d", v)
64 // SimpleExpectation holds an arbitrary check func, and implements the Expectation interface.
65 type SimpleExpectation struct {
66 check func(State) Verdict
70 // Check invokes e.check.
71 func (e SimpleExpectation) Check(s State) Verdict {
75 // Description returns e.descriptin.
76 func (e SimpleExpectation) Description() string {
80 // OnceMet returns an Expectation that, once the precondition is met, asserts
81 // that mustMeet is met.
82 func OnceMet(precondition Expectation, mustMeet Expectation) *SimpleExpectation {
83 check := func(s State) Verdict {
84 switch pre := precondition.Check(s); pre {
88 verdict := mustMeet.Check(s)
97 return &SimpleExpectation{
99 description: fmt.Sprintf("once %q is met, must have %q", precondition.Description(), mustMeet.Description()),
103 // ReadDiagnostics is an 'expectation' that is used to read diagnostics
104 // atomically. It is intended to be used with 'OnceMet'.
105 func ReadDiagnostics(fileName string, into *protocol.PublishDiagnosticsParams) *SimpleExpectation {
106 check := func(s State) Verdict {
107 diags, ok := s.diagnostics[fileName]
114 return &SimpleExpectation{
116 description: fmt.Sprintf("read diagnostics for %q", fileName),
120 // NoOutstandingWork asserts that there is no work initiated using the LSP
121 // $/progress API that has not completed.
122 func NoOutstandingWork() SimpleExpectation {
123 check := func(s State) Verdict {
124 if len(s.outstandingWork) == 0 {
129 return SimpleExpectation{
131 description: "no outstanding work",
135 // NoShowMessage asserts that the editor has not received a ShowMessage.
136 func NoShowMessage() SimpleExpectation {
137 check := func(s State) Verdict {
138 if len(s.showMessage) == 0 {
143 return SimpleExpectation{
145 description: "no ShowMessage received",
149 // ShownMessage asserts that the editor has received a ShownMessage with the
151 func ShownMessage(title string) SimpleExpectation {
152 check := func(s State) Verdict {
153 for _, m := range s.showMessage {
154 if strings.Contains(m.Message, title) {
160 return SimpleExpectation{
162 description: "received ShowMessage",
166 // ShowMessageRequest asserts that the editor has received a ShowMessageRequest
167 // with an action item that has the given title.
168 func ShowMessageRequest(title string) SimpleExpectation {
169 check := func(s State) Verdict {
170 if len(s.showMessageRequest) == 0 {
173 // Only check the most recent one.
174 m := s.showMessageRequest[len(s.showMessageRequest)-1]
175 if len(m.Actions) == 0 || len(m.Actions) > 1 {
178 if m.Actions[0].Title == title {
183 return SimpleExpectation{
185 description: "received ShowMessageRequest",
189 // DoneWithOpen expects all didOpen notifications currently sent by the editor
190 // to be completely processed.
191 func (e *Env) DoneWithOpen() Expectation {
192 opens := e.Editor.Stats().DidOpen
193 return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), opens)
196 // DoneWithChange expects all didChange notifications currently sent by the
197 // editor to be completely processed.
198 func (e *Env) DoneWithChange() Expectation {
199 changes := e.Editor.Stats().DidChange
200 return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), changes)
203 // DoneWithSave expects all didSave notifications currently sent by the editor
204 // to be completely processed.
205 func (e *Env) DoneWithSave() Expectation {
206 saves := e.Editor.Stats().DidSave
207 return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidSave), saves)
210 // DoneWithChangeWatchedFiles expects all didChangeWatchedFiles notifications
211 // currently sent by the editor to be completely processed.
212 func (e *Env) DoneWithChangeWatchedFiles() Expectation {
213 changes := e.Editor.Stats().DidChangeWatchedFiles
214 return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), changes)
217 // DoneWithClose expects all didClose notifications currently sent by the
218 // editor to be completely processed.
219 func (e *Env) DoneWithClose() Expectation {
220 changes := e.Editor.Stats().DidClose
221 return CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidClose), changes)
224 // CompletedWork expects a work item to have been completed >= atLeast times.
226 // Since the Progress API doesn't include any hidden metadata, we must use the
227 // progress notification title to identify the work we expect to be completed.
228 func CompletedWork(title string, atLeast uint64) SimpleExpectation {
229 check := func(s State) Verdict {
230 if s.completedWork[title] >= atLeast {
235 return SimpleExpectation{
237 description: fmt.Sprintf("completed work %q at least %d time(s)", title, atLeast),
241 // OutstandingWork expects a work item to be outstanding. The given title must
242 // be an exact match, whereas the given msg must only be contained in the work
244 func OutstandingWork(title, msg string) SimpleExpectation {
245 check := func(s State) Verdict {
246 for _, work := range s.outstandingWork {
247 if work.title == title && strings.Contains(work.msg, msg) {
253 return SimpleExpectation{
255 description: fmt.Sprintf("outstanding work: %s", title),
259 // LogExpectation is an expectation on the log messages received by the editor
261 type LogExpectation struct {
262 check func([]*protocol.LogMessageParams) Verdict
266 // Check implements the Expectation interface.
267 func (e LogExpectation) Check(s State) Verdict {
268 return e.check(s.logs)
271 // Description implements the Expectation interface.
272 func (e LogExpectation) Description() string {
276 // NoErrorLogs asserts that the client has not received any log messages of
278 func NoErrorLogs() LogExpectation {
279 return NoLogMatching(protocol.Error, "")
282 // LogMatching asserts that the client has received a log message
283 // of type typ matching the regexp re.
284 func LogMatching(typ protocol.MessageType, re string, count int) LogExpectation {
285 rec, err := regexp.Compile(re)
289 check := func(msgs []*protocol.LogMessageParams) Verdict {
291 for _, msg := range msgs {
292 if msg.Type == typ && rec.Match([]byte(msg.Message)) {
301 return LogExpectation{
303 description: fmt.Sprintf("log message matching %q", re),
307 // NoLogMatching asserts that the client has not received a log message
308 // of type typ matching the regexp re. If re is an empty string, any log
309 // message is considered a match.
310 func NoLogMatching(typ protocol.MessageType, re string) LogExpectation {
314 r, err = regexp.Compile(re)
319 check := func(msgs []*protocol.LogMessageParams) Verdict {
320 for _, msg := range msgs {
324 if r == nil || r.Match([]byte(msg.Message)) {
330 return LogExpectation{
332 description: fmt.Sprintf("no log message matching %q", re),
336 // RegistrationExpectation is an expectation on the capability registrations
337 // received by the editor from gopls.
338 type RegistrationExpectation struct {
339 check func([]*protocol.RegistrationParams) Verdict
343 // Check implements the Expectation interface.
344 func (e RegistrationExpectation) Check(s State) Verdict {
345 return e.check(s.registrations)
348 // Description implements the Expectation interface.
349 func (e RegistrationExpectation) Description() string {
353 // RegistrationMatching asserts that the client has received a capability
354 // registration matching the given regexp.
355 func RegistrationMatching(re string) RegistrationExpectation {
356 rec, err := regexp.Compile(re)
360 check := func(params []*protocol.RegistrationParams) Verdict {
361 for _, p := range params {
362 for _, r := range p.Registrations {
363 if rec.Match([]byte(r.Method)) {
370 return RegistrationExpectation{
372 description: fmt.Sprintf("registration matching %q", re),
376 // UnregistrationExpectation is an expectation on the capability
377 // unregistrations received by the editor from gopls.
378 type UnregistrationExpectation struct {
379 check func([]*protocol.UnregistrationParams) Verdict
383 // Check implements the Expectation interface.
384 func (e UnregistrationExpectation) Check(s State) Verdict {
385 return e.check(s.unregistrations)
388 // Description implements the Expectation interface.
389 func (e UnregistrationExpectation) Description() string {
393 // UnregistrationMatching asserts that the client has received an
394 // unregistration whose ID matches the given regexp.
395 func UnregistrationMatching(re string) UnregistrationExpectation {
396 rec, err := regexp.Compile(re)
400 check := func(params []*protocol.UnregistrationParams) Verdict {
401 for _, p := range params {
402 for _, r := range p.Unregisterations {
403 if rec.Match([]byte(r.Method)) {
410 return UnregistrationExpectation{
412 description: fmt.Sprintf("unregistration matching %q", re),
416 // A DiagnosticExpectation is a condition that must be met by the current set
417 // of diagnostics for a file.
418 type DiagnosticExpectation struct {
419 // optionally, the position of the diagnostic and the regex used to calculate it.
423 // optionally, the message that the diagnostic should contain.
426 // whether the expectation is that the diagnostic is present, or absent.
429 // path is the scratch workdir-relative path to the file being asserted on.
433 // Check implements the Expectation interface.
434 func (e DiagnosticExpectation) Check(s State) Verdict {
435 diags, ok := s.diagnostics[e.path]
444 for _, d := range diags.Diagnostics {
446 if d.Range.Start.Line != uint32(e.pos.Line) || d.Range.Start.Character != uint32(e.pos.Column) {
451 if !strings.Contains(d.Message, e.message) {
459 if found == e.present {
465 // Description implements the Expectation interface.
466 func (e DiagnosticExpectation) Description() string {
471 desc += " diagnostic"
473 desc += fmt.Sprintf(" at {line:%d, column:%d}", e.pos.Line, e.pos.Column)
475 desc += fmt.Sprintf(" (location of %q)", e.re)
479 desc += fmt.Sprintf(" with message %q", e.message)
484 // EmptyDiagnostics asserts that empty diagnostics are sent for the
485 // workspace-relative path name.
486 func EmptyDiagnostics(name string) Expectation {
487 check := func(s State) Verdict {
488 if diags := s.diagnostics[name]; diags != nil && len(diags.Diagnostics) == 0 {
493 return SimpleExpectation{
495 description: "empty diagnostics",
499 // NoDiagnostics asserts that no diagnostics are sent for the
500 // workspace-relative path name. It should be used primarily in conjunction
501 // with a OnceMet, as it has to check that all outstanding diagnostics have
502 // already been delivered.
503 func NoDiagnostics(name string) Expectation {
504 check := func(s State) Verdict {
505 if _, ok := s.diagnostics[name]; !ok {
510 return SimpleExpectation{
512 description: "no diagnostics",
516 // AnyDiagnosticAtCurrentVersion asserts that there is a diagnostic report for
517 // the current edited version of the buffer corresponding to the given
518 // workdir-relative pathname.
519 func (e *Env) AnyDiagnosticAtCurrentVersion(name string) Expectation {
520 version := e.Editor.BufferVersion(name)
521 check := func(s State) Verdict {
522 diags, ok := s.diagnostics[name]
523 if ok && diags.Version == int32(version) {
528 return SimpleExpectation{
530 description: fmt.Sprintf("any diagnostics at version %d", version),
534 // DiagnosticAtRegexp expects that there is a diagnostic entry at the start
535 // position matching the regexp search string re in the buffer specified by
536 // name. Note that this currently ignores the end position.
537 func (e *Env) DiagnosticAtRegexp(name, re string) DiagnosticExpectation {
539 pos := e.RegexpSearch(name, re)
540 return DiagnosticExpectation{path: name, pos: &pos, re: re, present: true}
543 // DiagnosticAtRegexpWithMessage is like DiagnosticAtRegexp, but it also
544 // checks for the content of the diagnostic message,
545 func (e *Env) DiagnosticAtRegexpWithMessage(name, re, msg string) DiagnosticExpectation {
547 pos := e.RegexpSearch(name, re)
548 return DiagnosticExpectation{path: name, pos: &pos, re: re, present: true, message: msg}
551 // DiagnosticAt asserts that there is a diagnostic entry at the position
552 // specified by line and col, for the workdir-relative path name.
553 func DiagnosticAt(name string, line, col int) DiagnosticExpectation {
554 return DiagnosticExpectation{path: name, pos: &fake.Pos{Line: line, Column: col}, present: true}
557 // NoDiagnosticAtRegexp expects that there is no diagnostic entry at the start
558 // position matching the regexp search string re in the buffer specified by
559 // name. Note that this currently ignores the end position.
560 // This should only be used in combination with OnceMet for a given condition,
561 // otherwise it may always succeed.
562 func (e *Env) NoDiagnosticAtRegexp(name, re string) DiagnosticExpectation {
564 pos := e.RegexpSearch(name, re)
565 return DiagnosticExpectation{path: name, pos: &pos, re: re, present: false}
568 // NoDiagnosticAt asserts that there is no diagnostic entry at the position
569 // specified by line and col, for the workdir-relative path name.
570 // This should only be used in combination with OnceMet for a given condition,
571 // otherwise it may always succeed.
572 func NoDiagnosticAt(name string, line, col int) DiagnosticExpectation {
573 return DiagnosticExpectation{path: name, pos: &fake.Pos{Line: line, Column: col}, present: false}
576 // NoDiagnosticWithMessage asserts that there is no diagnostic entry with the
579 // This should only be used in combination with OnceMet for a given condition,
580 // otherwise it may always succeed.
581 func NoDiagnosticWithMessage(name, msg string) DiagnosticExpectation {
582 return DiagnosticExpectation{path: name, message: msg, present: false}
585 // GoSum asserts that a "go.sum is out of sync" diagnostic for the given module
586 // (as formatted in a go.mod file, e.g. "example.com v1.0.0") is present.
587 func (e *Env) GoSumDiagnostic(name, module string) Expectation {
589 // In 1.16, go.sum diagnostics should appear on the relevant module. Earlier
590 // errors have no information and appear on the module declaration.
591 if testenv.Go1Point() >= 16 {
592 return e.DiagnosticAtRegexpWithMessage(name, module, "go.sum is out of sync")
594 return e.DiagnosticAtRegexpWithMessage(name, `module`, "go.sum is out of sync")