@@ -9,46 +9,118 @@ package servertest
9
9
import (
10
10
"context"
11
11
"fmt"
12
+ "io"
12
13
"net"
14
+ "sync"
13
15
14
16
"golang.org/x/tools/internal/jsonrpc2"
15
17
)
16
18
17
- // Server is a helper for executing tests against a remote jsonrpc2 connection.
18
- // Once initialized, its Addr field may be used to connect a jsonrpc2 client.
19
- type Server struct {
19
+ // Connector is the interface used to connect to a server.
20
+ type Connector interface {
21
+ Connect (context.Context ) * jsonrpc2.Conn
22
+ }
23
+
24
+ // TCPServer is a helper for executing tests against a remote jsonrpc2
25
+ // connection. Once initialized, its Addr field may be used to connect a
26
+ // jsonrpc2 client.
27
+ type TCPServer struct {
20
28
Addr string
21
29
22
30
ln net.Listener
31
+ cls * closerList
23
32
}
24
33
25
- // NewServer returns a new test server listening on local tcp port and serving
26
- // incoming jsonrpc2 streams using the provided stream server. It panics on any
27
- // error.
28
- func NewServer (ctx context.Context , server jsonrpc2.StreamServer ) * Server {
34
+ // NewTCPServer returns a new test server listening on local tcp port and
35
+ // serving incoming jsonrpc2 streams using the provided stream server. It
36
+ // panics on any error.
37
+ func NewTCPServer (ctx context.Context , server jsonrpc2.StreamServer ) * TCPServer {
29
38
ln , err := net .Listen ("tcp" , "127.0.0.1:0" )
30
39
if err != nil {
31
40
panic (fmt .Sprintf ("servertest: failed to listen: %v" , err ))
32
41
}
33
42
go jsonrpc2 .Serve (ctx , ln , server )
34
- return & Server {Addr : ln .Addr ().String (), ln : ln }
43
+ return & TCPServer {Addr : ln .Addr ().String (), ln : ln , cls : & closerList {} }
35
44
}
36
45
37
46
// Connect dials the test server and returns a jsonrpc2 Connection that is
38
47
// ready for use.
39
- func (s * Server ) Connect (ctx context.Context ) * jsonrpc2.Conn {
48
+ func (s * TCPServer ) Connect (ctx context.Context ) * jsonrpc2.Conn {
40
49
netConn , err := net .Dial ("tcp" , s .Addr )
41
50
if err != nil {
42
51
panic (fmt .Sprintf ("servertest: failed to connect to test instance: %v" , err ))
43
52
}
53
+ s .cls .add (func () {
54
+ netConn .Close ()
55
+ })
44
56
conn := jsonrpc2 .NewConn (jsonrpc2 .NewHeaderStream (netConn , netConn ))
45
57
go conn .Run (ctx )
46
58
return conn
47
59
}
48
60
49
- // Close is a placeholder for proper test server shutdown.
50
- // TODO: implement proper shutdown, which gracefully closes existing
51
- // connections to the test server.
52
- func (s * Server ) Close () error {
61
+ // Close closes all connected pipes.
62
+ func (s * TCPServer ) Close () error {
63
+ s .cls .closeAll ()
53
64
return nil
54
65
}
66
+
67
+ // PipeServer is a test server that handles connections over io.Pipes.
68
+ type PipeServer struct {
69
+ server jsonrpc2.StreamServer
70
+ cls * closerList
71
+ }
72
+
73
+ // NewPipeServer returns a test server that can be connected to via io.Pipes.
74
+ func NewPipeServer (ctx context.Context , server jsonrpc2.StreamServer ) * PipeServer {
75
+ return & PipeServer {server : server , cls : & closerList {}}
76
+ }
77
+
78
+ // Connect creates new io.Pipes and binds them to the underlying StreamServer.
79
+ func (s * PipeServer ) Connect (ctx context.Context ) * jsonrpc2.Conn {
80
+ // Pipes connect like this:
81
+ // Client🡒(sWriter)🡒(sReader)🡒Server
82
+ // 🡔(cReader)🡐(cWriter)🡗
83
+ sReader , sWriter := io .Pipe ()
84
+ cReader , cWriter := io .Pipe ()
85
+ s .cls .add (func () {
86
+ sReader .Close ()
87
+ sWriter .Close ()
88
+ cReader .Close ()
89
+ cWriter .Close ()
90
+ })
91
+ serverStream := jsonrpc2 .NewStream (sReader , cWriter )
92
+ go s .server .ServeStream (ctx , serverStream )
93
+
94
+ clientStream := jsonrpc2 .NewStream (cReader , sWriter )
95
+ clientConn := jsonrpc2 .NewConn (clientStream )
96
+ go clientConn .Run (ctx )
97
+ return clientConn
98
+ }
99
+
100
+ // Close closes all connected pipes.
101
+ func (s * PipeServer ) Close () error {
102
+ s .cls .closeAll ()
103
+ return nil
104
+ }
105
+
106
+ // closerList tracks closers to run when a testserver is closed. This is a
107
+ // convenience, so that callers don't have to worry about closing each
108
+ // connection.
109
+ type closerList struct {
110
+ mu sync.Mutex
111
+ closers []func ()
112
+ }
113
+
114
+ func (l * closerList ) add (closer func ()) {
115
+ l .mu .Lock ()
116
+ defer l .mu .Unlock ()
117
+ l .closers = append (l .closers , closer )
118
+ }
119
+
120
+ func (l * closerList ) closeAll () {
121
+ l .mu .Lock ()
122
+ defer l .mu .Unlock ()
123
+ for _ , closer := range l .closers {
124
+ closer ()
125
+ }
126
+ }
0 commit comments