blob: 042056e77773add8367c1063fa979eaf7592113f [file] [log] [blame]
// Copyright 2021 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 lsprpc_test
import (
"context"
"regexp"
"strings"
"testing"
"time"
"golang.org/x/tools/gopls/internal/protocol"
jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
. "golang.org/x/tools/gopls/internal/lsprpc"
)
// ServerBinder binds incoming connections to a new server.
type ServerBinder struct {
newServer ServerFunc
}
func NewServerBinder(newServer ServerFunc) *ServerBinder {
return &ServerBinder{newServer: newServer}
}
// streamServer used to have this method, but it was never used.
// TODO(adonovan): figure out whether we need any of this machinery
// and, if not, delete it. In the meantime, it's better that it sit
// in the test package with all the other mothballed machinery
// than in the production code where it would couple streamServer
// and ServerBinder.
/*
func (s *streamServer) Binder() *ServerBinder {
newServer := func(ctx context.Context, client protocol.ClientCloser) protocol.Server {
session := cache.NewSession(ctx, s.cache)
svr := s.serverForTest
if svr == nil {
options := settings.DefaultOptions(s.optionsOverrides)
svr = server.New(session, client, options)
if instance := debug.GetInstance(ctx); instance != nil {
instance.AddService(svr, session)
}
}
return svr
}
return NewServerBinder(newServer)
}
*/
func (b *ServerBinder) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions {
client := protocol.ClientDispatcherV2(conn)
server := b.newServer(ctx, client)
serverHandler := protocol.ServerHandlerV2(server)
// Wrap the server handler to inject the client into each request context, so
// that log events are reflected back to the client.
wrapped := jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) {
ctx = protocol.WithClient(ctx, client)
return serverHandler.Handle(ctx, req)
})
preempter := &Canceler{
Conn: conn,
}
return jsonrpc2_v2.ConnectionOptions{
Handler: wrapped,
Preempter: preempter,
}
}
type TestEnv struct {
Conns []*jsonrpc2_v2.Connection
Servers []*jsonrpc2_v2.Server
}
func (e *TestEnv) Shutdown(t *testing.T) {
for _, s := range e.Servers {
s.Shutdown()
}
for _, c := range e.Conns {
if err := c.Close(); err != nil {
t.Error(err)
}
}
for _, s := range e.Servers {
if err := s.Wait(); err != nil {
t.Error(err)
}
}
}
func (e *TestEnv) serve(ctx context.Context, t *testing.T, server jsonrpc2_v2.Binder) (jsonrpc2_v2.Listener, *jsonrpc2_v2.Server) {
l, err := jsonrpc2_v2.NetPipeListener(ctx)
if err != nil {
t.Fatal(err)
}
s := jsonrpc2_v2.NewServer(ctx, l, server)
e.Servers = append(e.Servers, s)
return l, s
}
func (e *TestEnv) dial(ctx context.Context, t *testing.T, dialer jsonrpc2_v2.Dialer, client jsonrpc2_v2.Binder, forwarded bool) *jsonrpc2_v2.Connection {
if forwarded {
l, _ := e.serve(ctx, t, NewForwardBinder(dialer))
dialer = l.Dialer()
}
conn, err := jsonrpc2_v2.Dial(ctx, dialer, client)
if err != nil {
t.Fatal(err)
}
e.Conns = append(e.Conns, conn)
return conn
}
func staticClientBinder(client protocol.Client) jsonrpc2_v2.Binder {
f := func(context.Context, protocol.Server) protocol.Client { return client }
return NewClientBinder(f)
}
func staticServerBinder(server protocol.Server) jsonrpc2_v2.Binder {
f := func(ctx context.Context, client protocol.ClientCloser) protocol.Server {
return server
}
return NewServerBinder(f)
}
func TestClientLoggingV2(t *testing.T) {
ctx := context.Background()
for name, forwarded := range map[string]bool{
"forwarded": true,
"standalone": false,
} {
t.Run(name, func(t *testing.T) {
client := FakeClient{Logs: make(chan string, 10)}
env := new(TestEnv)
defer env.Shutdown(t)
l, _ := env.serve(ctx, t, staticServerBinder(PingServer{}))
conn := env.dial(ctx, t, l.Dialer(), staticClientBinder(client), forwarded)
if err := protocol.ServerDispatcherV2(conn).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{}); err != nil {
t.Errorf("DidOpen: %v", err)
}
select {
case got := <-client.Logs:
want := "ping"
matched, err := regexp.MatchString(want, got)
if err != nil {
t.Fatal(err)
}
if !matched {
t.Errorf("got log %q, want a log containing %q", got, want)
}
case <-time.After(1 * time.Second):
t.Error("timeout waiting for client log")
}
})
}
}
func TestRequestCancellationV2(t *testing.T) {
ctx := context.Background()
for name, forwarded := range map[string]bool{
"forwarded": true,
"standalone": false,
} {
t.Run(name, func(t *testing.T) {
server := WaitableServer{
Started: make(chan struct{}),
Completed: make(chan error),
}
env := new(TestEnv)
defer env.Shutdown(t)
l, _ := env.serve(ctx, t, staticServerBinder(server))
client := FakeClient{Logs: make(chan string, 10)}
conn := env.dial(ctx, t, l.Dialer(), staticClientBinder(client), forwarded)
sd := protocol.ServerDispatcherV2(conn)
ctx, cancel := context.WithCancel(ctx)
result := make(chan error)
go func() {
_, err := sd.Hover(ctx, &protocol.HoverParams{})
result <- err
}()
// Wait for the Hover request to start.
<-server.Started
cancel()
if err := <-result; err == nil {
t.Error("nil error for cancelled Hover(), want non-nil")
}
if err := <-server.Completed; err == nil || !strings.Contains(err.Error(), "cancelled hover") {
t.Errorf("Hover(): unexpected server-side error %v", err)
}
})
}
}