1
0
mirror of https://github.com/golang/go synced 2024-11-18 15:14:44 -07:00
go/internal/jsonrpc2/servertest/servertest.go
Rob Findley 9b4b92067d internal/jsonrpc2/servertest: replace closerList with connList
closerList was already unnecessarily abstract, and tracking connections
will allow us to also wait for all connections to finish.

Share functionality by embedding this type in PipeServer, TCPServer.

Change-Id: Ib2cb2157c1477f904bc278aa91902f5c633afe13
Reviewed-on: https://go-review.googlesource.com/c/tools/+/238547
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2020-06-18 12:08:28 +00:00

120 lines
3.2 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 servertest provides utilities for running tests against a remote LSP
// server.
package servertest
import (
"context"
"fmt"
"net"
"strings"
"sync"
"golang.org/x/tools/internal/jsonrpc2"
)
// Connector is the interface used to connect to a server.
type Connector interface {
Connect(context.Context) jsonrpc2.Conn
}
// TCPServer is a helper for executing tests against a remote jsonrpc2
// connection. Once initialized, its Addr field may be used to connect a
// jsonrpc2 client.
type TCPServer struct {
*connList
Addr string
ln net.Listener
framer jsonrpc2.Framer
}
// NewTCPServer returns a new test server listening on local tcp port and
// serving incoming jsonrpc2 streams using the provided stream server. It
// panics on any error.
func NewTCPServer(ctx context.Context, server jsonrpc2.StreamServer, framer jsonrpc2.Framer) *TCPServer {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(fmt.Sprintf("servertest: failed to listen: %v", err))
}
if framer == nil {
framer = jsonrpc2.NewHeaderStream
}
go jsonrpc2.Serve(ctx, ln, server, 0)
return &TCPServer{Addr: ln.Addr().String(), ln: ln, framer: framer, connList: &connList{}}
}
// Connect dials the test server and returns a jsonrpc2 Connection that is
// ready for use.
func (s *TCPServer) Connect(ctx context.Context) jsonrpc2.Conn {
netConn, err := net.Dial("tcp", s.Addr)
if err != nil {
panic(fmt.Sprintf("servertest: failed to connect to test instance: %v", err))
}
conn := jsonrpc2.NewConn(s.framer(netConn))
s.add(conn)
return conn
}
// PipeServer is a test server that handles connections over io.Pipes.
type PipeServer struct {
*connList
server jsonrpc2.StreamServer
framer jsonrpc2.Framer
}
// NewPipeServer returns a test server that can be connected to via io.Pipes.
func NewPipeServer(ctx context.Context, server jsonrpc2.StreamServer, framer jsonrpc2.Framer) *PipeServer {
if framer == nil {
framer = jsonrpc2.NewRawStream
}
return &PipeServer{server: server, framer: framer, connList: &connList{}}
}
// Connect creates new io.Pipes and binds them to the underlying StreamServer.
func (s *PipeServer) Connect(ctx context.Context) jsonrpc2.Conn {
sPipe, cPipe := net.Pipe()
serverStream := s.framer(sPipe)
serverConn := jsonrpc2.NewConn(serverStream)
s.add(serverConn)
go s.server.ServeStream(ctx, serverConn)
clientStream := s.framer(cPipe)
clientConn := jsonrpc2.NewConn(clientStream)
s.add(clientConn)
return clientConn
}
// connList tracks closers to run when a testserver is closed. This is a
// convenience, so that callers don't have to worry about closing each
// connection.
type connList struct {
mu sync.Mutex
conns []jsonrpc2.Conn
}
func (l *connList) add(conn jsonrpc2.Conn) {
l.mu.Lock()
defer l.mu.Unlock()
l.conns = append(l.conns, conn)
}
func (l *connList) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
var errmsgs []string
for _, conn := range l.conns {
if err := conn.Close(); err != nil {
errmsgs = append(errmsgs, err.Error())
}
}
if len(errmsgs) > 0 {
return fmt.Errorf("closing errors:\n%s", strings.Join(errmsgs, "\n"))
}
return nil
}