1
0
mirror of https://github.com/golang/go synced 2024-11-05 22:46:12 -07:00
go/internal/jsonrpc2/servertest/servertest.go
Ian Cottrell 9089ff1aee internal/jsonrpc2: switch to building streams on top of net.Conn
This allows us to rely on higher level functionality like timeouts and
close cancelling pending reads cleanly.

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

116 lines
3.0 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"
"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 {
Addr string
ln net.Listener
cls *closerList
}
// 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) *TCPServer {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(fmt.Sprintf("servertest: failed to listen: %v", err))
}
go jsonrpc2.Serve(ctx, ln, server, 0)
return &TCPServer{Addr: ln.Addr().String(), ln: ln, cls: &closerList{}}
}
// 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))
}
s.cls.add(func() {
netConn.Close()
})
return jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
}
// Close closes all connected pipes.
func (s *TCPServer) Close() error {
s.cls.closeAll()
return nil
}
// PipeServer is a test server that handles connections over io.Pipes.
type PipeServer struct {
server jsonrpc2.StreamServer
cls *closerList
}
// NewPipeServer returns a test server that can be connected to via io.Pipes.
func NewPipeServer(ctx context.Context, server jsonrpc2.StreamServer) *PipeServer {
return &PipeServer{server: server, cls: &closerList{}}
}
// 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()
s.cls.add(func() {
sPipe.Close()
cPipe.Close()
})
serverStream := jsonrpc2.NewRawStream(sPipe)
go s.server.ServeStream(ctx, serverStream)
clientStream := jsonrpc2.NewRawStream(cPipe)
return jsonrpc2.NewConn(clientStream)
}
// Close closes all connected pipes.
func (s *PipeServer) Close() error {
s.cls.closeAll()
return nil
}
// closerList 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 closerList struct {
mu sync.Mutex
closers []func()
}
func (l *closerList) add(closer func()) {
l.mu.Lock()
defer l.mu.Unlock()
l.closers = append(l.closers, closer)
}
func (l *closerList) closeAll() {
l.mu.Lock()
defer l.mu.Unlock()
for _, closer := range l.closers {
closer()
}
}