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  }