|  | // 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 | 
|  | } |