-
Notifications
You must be signed in to change notification settings - Fork 312
/
servertest.go
119 lines (103 loc) · 3.23 KB
/
servertest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// 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"
"encr.dev/cli/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
}