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  }