mirror of https://github.com/golang/go.git
521 lines
15 KiB
Go
521 lines
15 KiB
Go
// 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"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/span"
|
|
)
|
|
|
|
// 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
|
|
// Description is a human-readable description of the expectation.
|
|
Description() string
|
|
}
|
|
|
|
var (
|
|
// InitialWorkspaceLoad is an expectation that the workspace initial load has
|
|
// completed. It is verified via workdone reporting.
|
|
InitialWorkspaceLoad = CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromInitialWorkspaceLoad), 1)
|
|
)
|
|
|
|
// 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
|
|
description string
|
|
}
|
|
|
|
// Check invokes e.check.
|
|
func (e SimpleExpectation) Check(s State) Verdict {
|
|
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 {
|
|
switch pre := precondition.Check(s); pre {
|
|
case Unmeetable:
|
|
return Unmeetable
|
|
case Met:
|
|
verdict := mustMeet.Check(s)
|
|
if verdict != Met {
|
|
return Unmeetable
|
|
}
|
|
return Met
|
|
default:
|
|
return Unmet
|
|
}
|
|
}
|
|
return &SimpleExpectation{
|
|
check: check,
|
|
description: fmt.Sprintf("once %q is met, must have %q", precondition.Description(), mustMeet.Description()),
|
|
}
|
|
}
|
|
|
|
// ReadDiagnostics is an 'expectation' that is used to read diagnostics
|
|
// atomically. It is intended to be used with 'OnceMet'.
|
|
func ReadDiagnostics(fileName string, into *protocol.PublishDiagnosticsParams) *SimpleExpectation {
|
|
check := func(s State) Verdict {
|
|
diags, ok := s.diagnostics[fileName]
|
|
if !ok {
|
|
return Unmeetable
|
|
}
|
|
*into = *diags
|
|
return Met
|
|
}
|
|
return &SimpleExpectation{
|
|
check: check,
|
|
description: fmt.Sprintf("read diagnostics for %q", fileName),
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
if len(s.outstandingWork) == 0 {
|
|
return Met
|
|
}
|
|
return Unmet
|
|
}
|
|
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 {
|
|
if len(s.showMessage) == 0 {
|
|
return Met
|
|
}
|
|
return Unmeetable
|
|
}
|
|
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 {
|
|
for _, m := range s.showMessage {
|
|
if strings.Contains(m.Message, title) {
|
|
return Met
|
|
}
|
|
}
|
|
return Unmet
|
|
}
|
|
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 {
|
|
if len(s.showMessageRequest) == 0 {
|
|
return Unmet
|
|
}
|
|
// Only check the most recent one.
|
|
m := s.showMessageRequest[len(s.showMessageRequest)-1]
|
|
if len(m.Actions) == 0 || len(m.Actions) > 1 {
|
|
return Unmet
|
|
}
|
|
if m.Actions[0].Title == title {
|
|
return Met
|
|
}
|
|
return Unmet
|
|
}
|
|
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 {
|
|
if s.completedWork[title] >= atLeast {
|
|
return Met
|
|
}
|
|
return Unmet
|
|
}
|
|
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
|
|
description string
|
|
}
|
|
|
|
// Check implements the Expectation interface.
|
|
func (e LogExpectation) Check(s State) Verdict {
|
|
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 {
|
|
var found int
|
|
for _, msg := range msgs {
|
|
if msg.Type == typ && rec.Match([]byte(msg.Message)) {
|
|
found++
|
|
}
|
|
}
|
|
if found == count {
|
|
return Met
|
|
}
|
|
return Unmet
|
|
}
|
|
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 {
|
|
for _, msg := range msgs {
|
|
if msg.Type != typ {
|
|
continue
|
|
}
|
|
if r == nil || r.Match([]byte(msg.Message)) {
|
|
return Unmeetable
|
|
}
|
|
}
|
|
return Met
|
|
}
|
|
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
|
|
description string
|
|
}
|
|
|
|
// Check implements the Expectation interface.
|
|
func (e RegistrationExpectation) Check(s State) Verdict {
|
|
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 {
|
|
for _, p := range params {
|
|
for _, r := range p.Registrations {
|
|
if rec.Match([]byte(r.Method)) {
|
|
return Met
|
|
}
|
|
}
|
|
}
|
|
return Unmet
|
|
}
|
|
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
|
|
description string
|
|
}
|
|
|
|
// Check implements the Expectation interface.
|
|
func (e UnregistrationExpectation) Check(s State) Verdict {
|
|
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 {
|
|
for _, p := range params {
|
|
for _, r := range p.Unregisterations {
|
|
if rec.Match([]byte(r.Method)) {
|
|
return Met
|
|
}
|
|
}
|
|
}
|
|
return Unmet
|
|
}
|
|
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 {
|
|
if diags, ok := s.diagnostics[e.path]; ok && e.isMet(diags) {
|
|
return Met
|
|
}
|
|
return Unmet
|
|
}
|
|
|
|
// 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 {
|
|
if diags := s.diagnostics[name]; diags != nil && len(diags.Diagnostics) == 0 {
|
|
return Met
|
|
}
|
|
return Unmet
|
|
}
|
|
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 {
|
|
if _, ok := s.diagnostics[name]; !ok {
|
|
return Met
|
|
}
|
|
return Unmet
|
|
}
|
|
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,
|
|
}
|
|
}
|
|
|
|
// NoDiagnosticWithMessage asserts that there is no diagnostic entry with the
|
|
// given message.
|
|
//
|
|
// This should only be used in combination with OnceMet for a given condition,
|
|
// otherwise it may always succeed.
|
|
func NoDiagnosticWithMessage(msg string) DiagnosticExpectation {
|
|
var uri span.URI
|
|
isMet := func(diags *protocol.PublishDiagnosticsParams) bool {
|
|
for _, d := range diags.Diagnostics {
|
|
if d.Message == msg {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
var path string
|
|
if uri != "" {
|
|
path = uri.Filename()
|
|
}
|
|
return DiagnosticExpectation{
|
|
isMet: isMet,
|
|
description: fmt.Sprintf("no diagnostic with message %s", msg),
|
|
path: path,
|
|
}
|
|
}
|