github.com/hashicorp/go-plugin@v1.6.0/testing.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package plugin 5 6 import ( 7 "bytes" 8 "context" 9 "io" 10 "net" 11 "net/rpc" 12 13 hclog "github.com/hashicorp/go-hclog" 14 "github.com/hashicorp/go-plugin/internal/grpcmux" 15 "github.com/mitchellh/go-testing-interface" 16 "google.golang.org/grpc" 17 ) 18 19 // TestOptions allows specifying options that can affect the behavior of the 20 // test functions 21 type TestOptions struct { 22 //ServerStdout causes the given value to be used in place of a blank buffer 23 //for RPCServer's Stdout 24 ServerStdout io.ReadCloser 25 26 //ServerStderr causes the given value to be used in place of a blank buffer 27 //for RPCServer's Stderr 28 ServerStderr io.ReadCloser 29 } 30 31 // The testing file contains test helpers that you can use outside of 32 // this package for making it easier to test plugins themselves. 33 34 // TestConn is a helper function for returning a client and server 35 // net.Conn connected to each other. 36 func TestConn(t testing.T) (net.Conn, net.Conn) { 37 // Listen to any local port. This listener will be closed 38 // after a single connection is established. 39 l, err := net.Listen("tcp", "127.0.0.1:0") 40 if err != nil { 41 t.Fatalf("err: %s", err) 42 } 43 44 // Start a goroutine to accept our client connection 45 var serverConn net.Conn 46 doneCh := make(chan struct{}) 47 go func() { 48 defer close(doneCh) 49 defer l.Close() 50 var err error 51 serverConn, err = l.Accept() 52 if err != nil { 53 t.Fatalf("err: %s", err) 54 } 55 }() 56 57 // Connect to the server 58 clientConn, err := net.Dial("tcp", l.Addr().String()) 59 if err != nil { 60 t.Fatalf("err: %s", err) 61 } 62 63 // Wait for the server side to acknowledge it has connected 64 <-doneCh 65 66 return clientConn, serverConn 67 } 68 69 // TestRPCConn returns a rpc client and server connected to each other. 70 func TestRPCConn(t testing.T) (*rpc.Client, *rpc.Server) { 71 clientConn, serverConn := TestConn(t) 72 73 server := rpc.NewServer() 74 go server.ServeConn(serverConn) 75 76 client := rpc.NewClient(clientConn) 77 return client, server 78 } 79 80 // TestPluginRPCConn returns a plugin RPC client and server that are connected 81 // together and configured. 82 func TestPluginRPCConn(t testing.T, ps map[string]Plugin, opts *TestOptions) (*RPCClient, *RPCServer) { 83 // Create two net.Conns we can use to shuttle our control connection 84 clientConn, serverConn := TestConn(t) 85 86 // Start up the server 87 server := &RPCServer{Plugins: ps, Stdout: new(bytes.Buffer), Stderr: new(bytes.Buffer)} 88 if opts != nil { 89 if opts.ServerStdout != nil { 90 server.Stdout = opts.ServerStdout 91 } 92 if opts.ServerStderr != nil { 93 server.Stderr = opts.ServerStderr 94 } 95 } 96 go server.ServeConn(serverConn) 97 98 // Connect the client to the server 99 client, err := NewRPCClient(clientConn, ps) 100 if err != nil { 101 t.Fatalf("err: %s", err) 102 } 103 104 return client, server 105 } 106 107 // TestGRPCConn returns a gRPC client conn and grpc server that are connected 108 // together and configured. The register function is used to register services 109 // prior to the Serve call. This is used to test gRPC connections. 110 func TestGRPCConn(t testing.T, register func(*grpc.Server)) (*grpc.ClientConn, *grpc.Server) { 111 // Create a listener 112 l, err := net.Listen("tcp", "127.0.0.1:0") 113 if err != nil { 114 t.Fatalf("err: %s", err) 115 } 116 117 server := grpc.NewServer() 118 register(server) 119 go server.Serve(l) 120 121 // Connect to the server 122 conn, err := grpc.Dial( 123 l.Addr().String(), 124 grpc.WithBlock(), 125 grpc.WithInsecure()) 126 if err != nil { 127 t.Fatalf("err: %s", err) 128 } 129 130 // Connection successful, close the listener 131 l.Close() 132 133 return conn, server 134 } 135 136 // TestPluginGRPCConn returns a plugin gRPC client and server that are connected 137 // together and configured. This is used to test gRPC connections. 138 func TestPluginGRPCConn(t testing.T, multiplex bool, ps map[string]Plugin) (*GRPCClient, *GRPCServer) { 139 // Create a listener 140 ln, err := serverListener(UnixSocketConfig{}) 141 if err != nil { 142 t.Fatal(err) 143 } 144 145 logger := hclog.New(&hclog.LoggerOptions{ 146 Level: hclog.Debug, 147 }) 148 149 // Start up the server 150 var muxer *grpcmux.GRPCServerMuxer 151 if multiplex { 152 muxer = grpcmux.NewGRPCServerMuxer(logger, ln) 153 ln = muxer 154 } 155 server := &GRPCServer{ 156 Plugins: ps, 157 DoneCh: make(chan struct{}), 158 Server: DefaultGRPCServer, 159 Stdout: new(bytes.Buffer), 160 Stderr: new(bytes.Buffer), 161 logger: logger, 162 muxer: muxer, 163 } 164 if err := server.Init(); err != nil { 165 t.Fatalf("err: %s", err) 166 } 167 go server.Serve(ln) 168 169 client := &Client{ 170 address: ln.Addr(), 171 protocol: ProtocolGRPC, 172 config: &ClientConfig{ 173 Plugins: ps, 174 GRPCBrokerMultiplex: multiplex, 175 }, 176 logger: logger, 177 } 178 179 grpcClient, err := newGRPCClient(context.Background(), client) 180 if err != nil { 181 t.Fatal(err) 182 } 183 184 return grpcClient, server 185 }