From bf5c620a1027ec21ad590ee56912bcf63a85cba1 Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 15 Sep 2020 21:38:50 -0400 Subject: [PATCH] gopls/internal/regtest: move expectations to their own file We've accumulated a lot of expectations, that could benefit from some cleanup. Move them to a new file, and reorganize a bit as some of the logical sections had gotten mixed up. This change is purely moving code, no other changes. Change-Id: Ib0a86a5cd686b167e555d5fae77d20d02cfd67a1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/255122 Run-TryBot: Robert Findley TryBot-Result: Go Bot gopls-CI: kokoro Reviewed-by: Rebecca Stambler Trust: Rebecca Stambler Trust: Robert Findley --- gopls/internal/regtest/env.go | 457 ------------------------- gopls/internal/regtest/expectation.go | 469 ++++++++++++++++++++++++++ 2 files changed, 469 insertions(+), 457 deletions(-) create mode 100644 gopls/internal/regtest/expectation.go diff --git a/gopls/internal/regtest/env.go b/gopls/internal/regtest/env.go index 356fbb521b..069a95df08 100644 --- a/gopls/internal/regtest/env.go +++ b/gopls/internal/regtest/env.go @@ -7,7 +7,6 @@ package regtest import ( "context" "fmt" - "regexp" "strings" "sync" "testing" @@ -281,462 +280,6 @@ func checkExpectations(s State, expectations []Expectation) (Verdict, string, [] return finalVerdict, summary.String(), metBy } -// An Expectation asserts that the state of the editor at a point in time -// matches an expected condition. This is used for signaling in tests when -// certain conditions in the editor are met. -type Expectation interface { - // Check determines whether the state of the editor satisfies the - // expectation, returning the results that met the condition. - Check(State) (Verdict, interface{}) - // Description is a human-readable description of the expectation. - Description() string -} - -// A Verdict is the result of checking an expectation against the current -// editor state. -type Verdict int - -// Order matters for the following constants: verdicts are sorted in order of -// decisiveness. -const ( - // Met indicates that an expectation is satisfied by the current state. - Met Verdict = iota - // Unmet indicates that an expectation is not currently met, but could be met - // in the future. - Unmet - // Unmeetable indicates that an expectation cannot be satisfied in the - // future. - Unmeetable -) - -// OnceMet returns an Expectation that, once the precondition is met, asserts -// that mustMeet is met. -func OnceMet(precondition Expectation, mustMeet Expectation) *SimpleExpectation { - check := func(s State) (Verdict, interface{}) { - switch pre, _ := precondition.Check(s); pre { - case Unmeetable: - return Unmeetable, nil - case Met: - verdict, metBy := mustMeet.Check(s) - if verdict != Met { - return Unmeetable, metBy - } - return Met, metBy - default: - return Unmet, nil - } - } - return &SimpleExpectation{ - check: check, - description: fmt.Sprintf("once %q is met, must have %q", precondition.Description(), mustMeet.Description()), - } -} - -func (v Verdict) String() string { - switch v { - case Met: - return "Met" - case Unmet: - return "Unmet" - case Unmeetable: - return "Unmeetable" - } - 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, interface{}) - description string -} - -// Check invokes e.check. -func (e SimpleExpectation) Check(s State) (Verdict, interface{}) { - 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, interface{}) { - if len(s.outstandingWork) == 0 { - return Met, nil - } - return Unmet, nil - } - return SimpleExpectation{ - check: check, - description: "no outstanding work", - } -} - -// NoShowMessage asserts that the editor has not received a ShowMessage. -func NoShowMessage() SimpleExpectation { - check := func(s State) (Verdict, interface{}) { - if len(s.showMessage) == 0 { - return Met, "no ShowMessage" - } - return Unmeetable, nil - } - return SimpleExpectation{ - check: check, - description: "no ShowMessage received", - } -} - -// ShownMessage asserts that the editor has received a ShownMessage with the -// given title. -func ShownMessage(title string) SimpleExpectation { - check := func(s State) (Verdict, interface{}) { - for _, m := range s.showMessage { - if strings.Contains(m.Message, title) { - return Met, m - } - } - return Unmet, nil - } - return SimpleExpectation{ - check: check, - description: "received ShowMessage", - } -} - -// ShowMessageRequest asserts that the editor has received a ShowMessageRequest -// with an action item that has the given title. -func ShowMessageRequest(title string) SimpleExpectation { - check := func(s State) (Verdict, interface{}) { - if len(s.showMessageRequest) == 0 { - return Unmet, nil - } - // Only check the most recent one. - m := s.showMessageRequest[len(s.showMessageRequest)-1] - if len(m.Actions) == 0 || len(m.Actions) > 1 { - return Unmet, nil - } - if m.Actions[0].Title == title { - return Met, m.Actions[0] - } - return Unmet, nil - } - return SimpleExpectation{ - check: check, - description: "received ShowMessageRequest", - } -} - -// CompletedWork expects a work item to have been completed >= atLeast times. -// -// Since the Progress API doesn't include any hidden metadata, we must use the -// progress notification title to identify the work we expect to be completed. -func CompletedWork(title string, atLeast int) SimpleExpectation { - check := func(s State) (Verdict, interface{}) { - if s.completedWork[title] >= atLeast { - return Met, title - } - return Unmet, nil - } - return SimpleExpectation{ - check: check, - description: fmt.Sprintf("completed work %q at least %d time(s)", title, atLeast), - } -} - -// LogExpectation is an expectation on the log messages received by the editor -// from gopls. -type LogExpectation struct { - check func([]*protocol.LogMessageParams) (Verdict, interface{}) - description string -} - -// Check implements the Expectation interface. -func (e LogExpectation) Check(s State) (Verdict, interface{}) { - return e.check(s.logs) -} - -// Description implements the Expectation interface. -func (e LogExpectation) Description() string { - return e.description -} - -// NoErrorLogs asserts that the client has not received any log messages of -// error severity. -func NoErrorLogs() LogExpectation { - return NoLogMatching(protocol.Error, "") -} - -// LogMatching asserts that the client has received a log message -// of type typ matching the regexp re. -func LogMatching(typ protocol.MessageType, re string, count int) LogExpectation { - rec, err := regexp.Compile(re) - if err != nil { - panic(err) - } - check := func(msgs []*protocol.LogMessageParams) (Verdict, interface{}) { - var found int - for _, msg := range msgs { - if msg.Type == typ && rec.Match([]byte(msg.Message)) { - found++ - } - } - if found == count { - return Met, nil - } - return Unmet, nil - } - return LogExpectation{ - check: check, - description: fmt.Sprintf("log message matching %q", re), - } -} - -// NoLogMatching asserts that the client has not received a log message -// of type typ matching the regexp re. If re is an empty string, any log -// message is considered a match. -func NoLogMatching(typ protocol.MessageType, re string) LogExpectation { - var r *regexp.Regexp - if re != "" { - var err error - r, err = regexp.Compile(re) - if err != nil { - panic(err) - } - } - check := func(msgs []*protocol.LogMessageParams) (Verdict, interface{}) { - for _, msg := range msgs { - if msg.Type != typ { - continue - } - if r == nil || r.Match([]byte(msg.Message)) { - return Unmeetable, nil - } - } - return Met, nil - } - return LogExpectation{ - check: check, - description: fmt.Sprintf("no log message matching %q", re), - } -} - -// RegistrationExpectation is an expectation on the capability registrations -// received by the editor from gopls. -type RegistrationExpectation struct { - check func([]*protocol.RegistrationParams) (Verdict, interface{}) - description string -} - -// Check implements the Expectation interface. -func (e RegistrationExpectation) Check(s State) (Verdict, interface{}) { - return e.check(s.registrations) -} - -// Description implements the Expectation interface. -func (e RegistrationExpectation) Description() string { - return e.description -} - -// RegistrationMatching asserts that the client has received a capability -// registration matching the given regexp. -func RegistrationMatching(re string) RegistrationExpectation { - rec, err := regexp.Compile(re) - if err != nil { - panic(err) - } - check := func(params []*protocol.RegistrationParams) (Verdict, interface{}) { - for _, p := range params { - for _, r := range p.Registrations { - if rec.Match([]byte(r.Method)) { - return Met, r - } - } - } - return Unmet, nil - } - return RegistrationExpectation{ - check: check, - description: fmt.Sprintf("registration matching %q", re), - } -} - -// UnregistrationExpectation is an expectation on the capability -// unregistrations received by the editor from gopls. -type UnregistrationExpectation struct { - check func([]*protocol.UnregistrationParams) (Verdict, interface{}) - description string -} - -// Check implements the Expectation interface. -func (e UnregistrationExpectation) Check(s State) (Verdict, interface{}) { - return e.check(s.unregistrations) -} - -// Description implements the Expectation interface. -func (e UnregistrationExpectation) Description() string { - return e.description -} - -// UnregistrationMatching asserts that the client has received an -// unregistration whose ID matches the given regexp. -func UnregistrationMatching(re string) UnregistrationExpectation { - rec, err := regexp.Compile(re) - if err != nil { - panic(err) - } - check := func(params []*protocol.UnregistrationParams) (Verdict, interface{}) { - for _, p := range params { - for _, r := range p.Unregisterations { - if rec.Match([]byte(r.Method)) { - return Met, r - } - } - } - return Unmet, nil - } - return UnregistrationExpectation{ - check: check, - description: fmt.Sprintf("unregistration matching %q", re), - } -} - -// A DiagnosticExpectation is a condition that must be met by the current set -// of diagnostics for a file. -type DiagnosticExpectation struct { - // IsMet determines whether the diagnostics for this file version satisfy our - // expectation. - isMet func(*protocol.PublishDiagnosticsParams) bool - // Description is a human-readable description of the diagnostic expectation. - description string - // Path is the scratch workdir-relative path to the file being asserted on. - path string -} - -// Check implements the Expectation interface. -func (e DiagnosticExpectation) Check(s State) (Verdict, interface{}) { - if diags, ok := s.diagnostics[e.path]; ok && e.isMet(diags) { - return Met, diags - } - return Unmet, nil -} - -// Description implements the Expectation interface. -func (e DiagnosticExpectation) Description() string { - return fmt.Sprintf("%s: %s", e.path, e.description) -} - -// EmptyDiagnostics asserts that empty diagnostics are sent for the -// workspace-relative path name. -func EmptyDiagnostics(name string) Expectation { - check := func(s State) (Verdict, interface{}) { - if diags := s.diagnostics[name]; diags != nil && len(diags.Diagnostics) == 0 { - return Met, nil - } - return Unmet, nil - } - return SimpleExpectation{ - check: check, - description: "empty diagnostics", - } -} - -// NoDiagnostics asserts that no diagnostics are sent for the -// workspace-relative path name. It should be used primarily in conjunction -// with a OnceMet, as it has to check that all outstanding diagnostics have -// already been delivered. -func NoDiagnostics(name string) Expectation { - check := func(s State) (Verdict, interface{}) { - if _, ok := s.diagnostics[name]; !ok { - return Met, nil - } - return Unmet, nil - } - return SimpleExpectation{ - check: check, - description: "no diagnostics", - } -} - -// AnyDiagnosticAtCurrentVersion asserts that there is a diagnostic report for -// the current edited version of the buffer corresponding to the given -// workdir-relative pathname. -func (e *Env) AnyDiagnosticAtCurrentVersion(name string) DiagnosticExpectation { - version := e.Editor.BufferVersion(name) - isMet := func(diags *protocol.PublishDiagnosticsParams) bool { - return int(diags.Version) == version - } - return DiagnosticExpectation{ - isMet: isMet, - description: fmt.Sprintf("any diagnostics at version %d", version), - path: name, - } -} - -// DiagnosticAtRegexp expects that there is a diagnostic entry at the start -// position matching the regexp search string re in the buffer specified by -// name. Note that this currently ignores the end position. -func (e *Env) DiagnosticAtRegexp(name, re string) DiagnosticExpectation { - e.T.Helper() - pos := e.RegexpSearch(name, re) - expectation := DiagnosticAt(name, pos.Line, pos.Column) - expectation.description += fmt.Sprintf(" (location of %q)", re) - return expectation -} - -// DiagnosticAt asserts that there is a diagnostic entry at the position -// specified by line and col, for the workdir-relative path name. -func DiagnosticAt(name string, line, col int) DiagnosticExpectation { - isMet := func(diags *protocol.PublishDiagnosticsParams) bool { - for _, d := range diags.Diagnostics { - if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) { - return true - } - } - return false - } - return DiagnosticExpectation{ - isMet: isMet, - description: fmt.Sprintf("diagnostic at {line:%d, column:%d}", line, col), - path: name, - } -} - -// NoDiagnosticAtRegexp expects that there is no diagnostic entry at the start -// position matching the regexp search string re in the buffer specified by -// name. Note that this currently ignores the end position. -// This should only be used in combination with OnceMet for a given condition, -// otherwise it may always succeed. -func (e *Env) NoDiagnosticAtRegexp(name, re string) DiagnosticExpectation { - e.T.Helper() - pos := e.RegexpSearch(name, re) - expectation := NoDiagnosticAt(name, pos.Line, pos.Column) - expectation.description += fmt.Sprintf(" (location of %q)", re) - return expectation -} - -// NoDiagnosticAt asserts that there is no diagnostic entry at the position -// specified by line and col, for the workdir-relative path name. -// This should only be used in combination with OnceMet for a given condition, -// otherwise it may always succeed. -func NoDiagnosticAt(name string, line, col int) DiagnosticExpectation { - isMet := func(diags *protocol.PublishDiagnosticsParams) bool { - for _, d := range diags.Diagnostics { - if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) { - return false - } - } - return true - } - return DiagnosticExpectation{ - isMet: isMet, - description: fmt.Sprintf("no diagnostic at {line:%d, column:%d}", line, col), - path: name, - } -} - // DiagnosticsFor returns the current diagnostics for the file. It is useful // after waiting on AnyDiagnosticAtCurrentVersion, when the desired diagnostic // is not simply described by DiagnosticAt. diff --git a/gopls/internal/regtest/expectation.go b/gopls/internal/regtest/expectation.go new file mode 100644 index 0000000000..19f5ff2c68 --- /dev/null +++ b/gopls/internal/regtest/expectation.go @@ -0,0 +1,469 @@ +// 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 ( + "fmt" + "regexp" + "strings" + + "golang.org/x/tools/internal/lsp/protocol" +) + +// An Expectation asserts that the state of the editor at a point in time +// matches an expected condition. This is used for signaling in tests when +// certain conditions in the editor are met. +type Expectation interface { + // Check determines whether the state of the editor satisfies the + // expectation, returning the results that met the condition. + Check(State) (Verdict, interface{}) + // Description is a human-readable description of the expectation. + Description() string +} + +// A Verdict is the result of checking an expectation against the current +// editor state. +type Verdict int + +// Order matters for the following constants: verdicts are sorted in order of +// decisiveness. +const ( + // Met indicates that an expectation is satisfied by the current state. + Met Verdict = iota + // Unmet indicates that an expectation is not currently met, but could be met + // in the future. + Unmet + // Unmeetable indicates that an expectation cannot be satisfied in the + // future. + Unmeetable +) + +func (v Verdict) String() string { + switch v { + case Met: + return "Met" + case Unmet: + return "Unmet" + case Unmeetable: + return "Unmeetable" + } + 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, interface{}) + description string +} + +// Check invokes e.check. +func (e SimpleExpectation) Check(s State) (Verdict, interface{}) { + return e.check(s) +} + +// Description returns e.descriptin. +func (e SimpleExpectation) Description() string { + return e.description +} + +// OnceMet returns an Expectation that, once the precondition is met, asserts +// that mustMeet is met. +func OnceMet(precondition Expectation, mustMeet Expectation) *SimpleExpectation { + check := func(s State) (Verdict, interface{}) { + switch pre, _ := precondition.Check(s); pre { + case Unmeetable: + return Unmeetable, nil + case Met: + verdict, metBy := mustMeet.Check(s) + if verdict != Met { + return Unmeetable, metBy + } + return Met, metBy + default: + return Unmet, nil + } + } + return &SimpleExpectation{ + check: check, + description: fmt.Sprintf("once %q is met, must have %q", precondition.Description(), mustMeet.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, interface{}) { + if len(s.outstandingWork) == 0 { + return Met, nil + } + return Unmet, nil + } + return SimpleExpectation{ + check: check, + description: "no outstanding work", + } +} + +// NoShowMessage asserts that the editor has not received a ShowMessage. +func NoShowMessage() SimpleExpectation { + check := func(s State) (Verdict, interface{}) { + if len(s.showMessage) == 0 { + return Met, "no ShowMessage" + } + return Unmeetable, nil + } + return SimpleExpectation{ + check: check, + description: "no ShowMessage received", + } +} + +// ShownMessage asserts that the editor has received a ShownMessage with the +// given title. +func ShownMessage(title string) SimpleExpectation { + check := func(s State) (Verdict, interface{}) { + for _, m := range s.showMessage { + if strings.Contains(m.Message, title) { + return Met, m + } + } + return Unmet, nil + } + return SimpleExpectation{ + check: check, + description: "received ShowMessage", + } +} + +// ShowMessageRequest asserts that the editor has received a ShowMessageRequest +// with an action item that has the given title. +func ShowMessageRequest(title string) SimpleExpectation { + check := func(s State) (Verdict, interface{}) { + if len(s.showMessageRequest) == 0 { + return Unmet, nil + } + // Only check the most recent one. + m := s.showMessageRequest[len(s.showMessageRequest)-1] + if len(m.Actions) == 0 || len(m.Actions) > 1 { + return Unmet, nil + } + if m.Actions[0].Title == title { + return Met, m.Actions[0] + } + return Unmet, nil + } + return SimpleExpectation{ + check: check, + description: "received ShowMessageRequest", + } +} + +// CompletedWork expects a work item to have been completed >= atLeast times. +// +// Since the Progress API doesn't include any hidden metadata, we must use the +// progress notification title to identify the work we expect to be completed. +func CompletedWork(title string, atLeast int) SimpleExpectation { + check := func(s State) (Verdict, interface{}) { + if s.completedWork[title] >= atLeast { + return Met, title + } + return Unmet, nil + } + return SimpleExpectation{ + check: check, + description: fmt.Sprintf("completed work %q at least %d time(s)", title, atLeast), + } +} + +// LogExpectation is an expectation on the log messages received by the editor +// from gopls. +type LogExpectation struct { + check func([]*protocol.LogMessageParams) (Verdict, interface{}) + description string +} + +// Check implements the Expectation interface. +func (e LogExpectation) Check(s State) (Verdict, interface{}) { + return e.check(s.logs) +} + +// Description implements the Expectation interface. +func (e LogExpectation) Description() string { + return e.description +} + +// NoErrorLogs asserts that the client has not received any log messages of +// error severity. +func NoErrorLogs() LogExpectation { + return NoLogMatching(protocol.Error, "") +} + +// LogMatching asserts that the client has received a log message +// of type typ matching the regexp re. +func LogMatching(typ protocol.MessageType, re string, count int) LogExpectation { + rec, err := regexp.Compile(re) + if err != nil { + panic(err) + } + check := func(msgs []*protocol.LogMessageParams) (Verdict, interface{}) { + var found int + for _, msg := range msgs { + if msg.Type == typ && rec.Match([]byte(msg.Message)) { + found++ + } + } + if found == count { + return Met, nil + } + return Unmet, nil + } + return LogExpectation{ + check: check, + description: fmt.Sprintf("log message matching %q", re), + } +} + +// NoLogMatching asserts that the client has not received a log message +// of type typ matching the regexp re. If re is an empty string, any log +// message is considered a match. +func NoLogMatching(typ protocol.MessageType, re string) LogExpectation { + var r *regexp.Regexp + if re != "" { + var err error + r, err = regexp.Compile(re) + if err != nil { + panic(err) + } + } + check := func(msgs []*protocol.LogMessageParams) (Verdict, interface{}) { + for _, msg := range msgs { + if msg.Type != typ { + continue + } + if r == nil || r.Match([]byte(msg.Message)) { + return Unmeetable, nil + } + } + return Met, nil + } + return LogExpectation{ + check: check, + description: fmt.Sprintf("no log message matching %q", re), + } +} + +// RegistrationExpectation is an expectation on the capability registrations +// received by the editor from gopls. +type RegistrationExpectation struct { + check func([]*protocol.RegistrationParams) (Verdict, interface{}) + description string +} + +// Check implements the Expectation interface. +func (e RegistrationExpectation) Check(s State) (Verdict, interface{}) { + return e.check(s.registrations) +} + +// Description implements the Expectation interface. +func (e RegistrationExpectation) Description() string { + return e.description +} + +// RegistrationMatching asserts that the client has received a capability +// registration matching the given regexp. +func RegistrationMatching(re string) RegistrationExpectation { + rec, err := regexp.Compile(re) + if err != nil { + panic(err) + } + check := func(params []*protocol.RegistrationParams) (Verdict, interface{}) { + for _, p := range params { + for _, r := range p.Registrations { + if rec.Match([]byte(r.Method)) { + return Met, r + } + } + } + return Unmet, nil + } + return RegistrationExpectation{ + check: check, + description: fmt.Sprintf("registration matching %q", re), + } +} + +// UnregistrationExpectation is an expectation on the capability +// unregistrations received by the editor from gopls. +type UnregistrationExpectation struct { + check func([]*protocol.UnregistrationParams) (Verdict, interface{}) + description string +} + +// Check implements the Expectation interface. +func (e UnregistrationExpectation) Check(s State) (Verdict, interface{}) { + return e.check(s.unregistrations) +} + +// Description implements the Expectation interface. +func (e UnregistrationExpectation) Description() string { + return e.description +} + +// UnregistrationMatching asserts that the client has received an +// unregistration whose ID matches the given regexp. +func UnregistrationMatching(re string) UnregistrationExpectation { + rec, err := regexp.Compile(re) + if err != nil { + panic(err) + } + check := func(params []*protocol.UnregistrationParams) (Verdict, interface{}) { + for _, p := range params { + for _, r := range p.Unregisterations { + if rec.Match([]byte(r.Method)) { + return Met, r + } + } + } + return Unmet, nil + } + return UnregistrationExpectation{ + check: check, + description: fmt.Sprintf("unregistration matching %q", re), + } +} + +// A DiagnosticExpectation is a condition that must be met by the current set +// of diagnostics for a file. +type DiagnosticExpectation struct { + // IsMet determines whether the diagnostics for this file version satisfy our + // expectation. + isMet func(*protocol.PublishDiagnosticsParams) bool + // Description is a human-readable description of the diagnostic expectation. + description string + // Path is the scratch workdir-relative path to the file being asserted on. + path string +} + +// Check implements the Expectation interface. +func (e DiagnosticExpectation) Check(s State) (Verdict, interface{}) { + if diags, ok := s.diagnostics[e.path]; ok && e.isMet(diags) { + return Met, diags + } + return Unmet, nil +} + +// Description implements the Expectation interface. +func (e DiagnosticExpectation) Description() string { + return fmt.Sprintf("%s: %s", e.path, e.description) +} + +// EmptyDiagnostics asserts that empty diagnostics are sent for the +// workspace-relative path name. +func EmptyDiagnostics(name string) Expectation { + check := func(s State) (Verdict, interface{}) { + if diags := s.diagnostics[name]; diags != nil && len(diags.Diagnostics) == 0 { + return Met, nil + } + return Unmet, nil + } + return SimpleExpectation{ + check: check, + description: "empty diagnostics", + } +} + +// NoDiagnostics asserts that no diagnostics are sent for the +// workspace-relative path name. It should be used primarily in conjunction +// with a OnceMet, as it has to check that all outstanding diagnostics have +// already been delivered. +func NoDiagnostics(name string) Expectation { + check := func(s State) (Verdict, interface{}) { + if _, ok := s.diagnostics[name]; !ok { + return Met, nil + } + return Unmet, nil + } + return SimpleExpectation{ + check: check, + description: "no diagnostics", + } +} + +// AnyDiagnosticAtCurrentVersion asserts that there is a diagnostic report for +// the current edited version of the buffer corresponding to the given +// workdir-relative pathname. +func (e *Env) AnyDiagnosticAtCurrentVersion(name string) DiagnosticExpectation { + version := e.Editor.BufferVersion(name) + isMet := func(diags *protocol.PublishDiagnosticsParams) bool { + return int(diags.Version) == version + } + return DiagnosticExpectation{ + isMet: isMet, + description: fmt.Sprintf("any diagnostics at version %d", version), + path: name, + } +} + +// DiagnosticAtRegexp expects that there is a diagnostic entry at the start +// position matching the regexp search string re in the buffer specified by +// name. Note that this currently ignores the end position. +func (e *Env) DiagnosticAtRegexp(name, re string) DiagnosticExpectation { + e.T.Helper() + pos := e.RegexpSearch(name, re) + expectation := DiagnosticAt(name, pos.Line, pos.Column) + expectation.description += fmt.Sprintf(" (location of %q)", re) + return expectation +} + +// DiagnosticAt asserts that there is a diagnostic entry at the position +// specified by line and col, for the workdir-relative path name. +func DiagnosticAt(name string, line, col int) DiagnosticExpectation { + isMet := func(diags *protocol.PublishDiagnosticsParams) bool { + for _, d := range diags.Diagnostics { + if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) { + return true + } + } + return false + } + return DiagnosticExpectation{ + isMet: isMet, + description: fmt.Sprintf("diagnostic at {line:%d, column:%d}", line, col), + path: name, + } +} + +// NoDiagnosticAtRegexp expects that there is no diagnostic entry at the start +// position matching the regexp search string re in the buffer specified by +// name. Note that this currently ignores the end position. +// This should only be used in combination with OnceMet for a given condition, +// otherwise it may always succeed. +func (e *Env) NoDiagnosticAtRegexp(name, re string) DiagnosticExpectation { + e.T.Helper() + pos := e.RegexpSearch(name, re) + expectation := NoDiagnosticAt(name, pos.Line, pos.Column) + expectation.description += fmt.Sprintf(" (location of %q)", re) + return expectation +} + +// NoDiagnosticAt asserts that there is no diagnostic entry at the position +// specified by line and col, for the workdir-relative path name. +// This should only be used in combination with OnceMet for a given condition, +// otherwise it may always succeed. +func NoDiagnosticAt(name string, line, col int) DiagnosticExpectation { + isMet := func(diags *protocol.PublishDiagnosticsParams) bool { + for _, d := range diags.Diagnostics { + if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) { + return false + } + } + return true + } + return DiagnosticExpectation{ + isMet: isMet, + description: fmt.Sprintf("no diagnostic at {line:%d, column:%d}", line, col), + path: name, + } +}