github.com/hashicorp/go-plugin@v1.6.0/client_test.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  	"crypto/sha256"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"net"
    16  	"os"
    17  	"os/exec"
    18  	"path/filepath"
    19  	"strings"
    20  	"sync"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/hashicorp/go-hclog"
    25  	"github.com/hashicorp/go-plugin/internal/cmdrunner"
    26  	"github.com/hashicorp/go-plugin/runner"
    27  )
    28  
    29  func TestClient(t *testing.T) {
    30  	process := helperProcess("mock")
    31  	c := NewClient(&ClientConfig{
    32  		Cmd:             process,
    33  		HandshakeConfig: testHandshake,
    34  		Plugins:         testPluginMap,
    35  	})
    36  	defer c.Kill()
    37  
    38  	// Test that it parses the proper address
    39  	addr, err := c.Start()
    40  	if err != nil {
    41  		t.Fatalf("err should be nil, got %s", err)
    42  	}
    43  
    44  	if addr.Network() != "tcp" {
    45  		t.Fatalf("bad: %#v", addr)
    46  	}
    47  
    48  	if addr.String() != ":1234" {
    49  		t.Fatalf("bad: %#v", addr)
    50  	}
    51  
    52  	// Test that it exits properly if killed
    53  	c.Kill()
    54  
    55  	// Test that it knows it is exited
    56  	if !c.Exited() {
    57  		t.Fatal("should say client has exited")
    58  	}
    59  
    60  	// this test isn't expected to get a client
    61  	if !c.killed() {
    62  		t.Fatal("Client should have failed")
    63  	}
    64  }
    65  
    66  // This tests a bug where Kill would start
    67  func TestClient_killStart(t *testing.T) {
    68  	// Create a temporary dir to store the result file
    69  	td := t.TempDir()
    70  	defer os.RemoveAll(td)
    71  
    72  	// Start the client
    73  	path := filepath.Join(td, "booted")
    74  	process := helperProcess("bad-version", path)
    75  	c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake})
    76  	defer c.Kill()
    77  
    78  	// Verify our path doesn't exist
    79  	if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
    80  		t.Fatalf("bad: %s", err)
    81  	}
    82  
    83  	// Test that it parses the proper address
    84  	if _, err := c.Start(); err == nil {
    85  		t.Fatal("expected error")
    86  	}
    87  
    88  	// Verify we started
    89  	if _, err := os.Stat(path); err != nil {
    90  		t.Fatalf("bad: %s", err)
    91  	}
    92  	if err := os.Remove(path); err != nil {
    93  		t.Fatalf("bad: %s", err)
    94  	}
    95  
    96  	// Test that Kill does nothing really
    97  	c.Kill()
    98  
    99  	// Test that it knows it is exited
   100  	if !c.Exited() {
   101  		t.Fatal("should say client has exited")
   102  	}
   103  
   104  	if !c.killed() {
   105  		t.Fatal("process should have failed")
   106  	}
   107  
   108  	// Verify our path doesn't exist
   109  	if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
   110  		t.Fatalf("bad: %s", err)
   111  	}
   112  }
   113  
   114  func TestClient_testCleanup(t *testing.T) {
   115  	// Create a temporary dir to store the result file
   116  	td := t.TempDir()
   117  	defer os.RemoveAll(td)
   118  
   119  	// Create a path that the helper process will write on cleanup
   120  	path := filepath.Join(td, "output")
   121  
   122  	// Test the cleanup
   123  	process := helperProcess("cleanup", path)
   124  	c := NewClient(&ClientConfig{
   125  		Cmd:             process,
   126  		HandshakeConfig: testHandshake,
   127  		Plugins:         testPluginMap,
   128  	})
   129  
   130  	// Grab the client so the process starts
   131  	if _, err := c.Client(); err != nil {
   132  		c.Kill()
   133  		t.Fatalf("err: %s", err)
   134  	}
   135  
   136  	// Kill it gracefully
   137  	c.Kill()
   138  
   139  	// Test for the file
   140  	if _, err := os.Stat(path); err != nil {
   141  		t.Fatalf("err: %s", err)
   142  	}
   143  }
   144  
   145  func TestClient_testInterface(t *testing.T) {
   146  	process := helperProcess("test-interface")
   147  	c := NewClient(&ClientConfig{
   148  		Cmd:             process,
   149  		HandshakeConfig: testHandshake,
   150  		Plugins:         testPluginMap,
   151  	})
   152  	defer c.Kill()
   153  
   154  	// Grab the RPC client
   155  	client, err := c.Client()
   156  	if err != nil {
   157  		t.Fatalf("err should be nil, got %s", err)
   158  	}
   159  
   160  	// Grab the impl
   161  	raw, err := client.Dispense("test")
   162  	if err != nil {
   163  		t.Fatalf("err should be nil, got %s", err)
   164  	}
   165  
   166  	impl, ok := raw.(testInterface)
   167  	if !ok {
   168  		t.Fatalf("bad: %#v", raw)
   169  	}
   170  
   171  	result := impl.Double(21)
   172  	if result != 42 {
   173  		t.Fatalf("bad: %#v", result)
   174  	}
   175  
   176  	// Kill it
   177  	c.Kill()
   178  
   179  	// Test that it knows it is exited
   180  	if !c.Exited() {
   181  		t.Fatal("should say client has exited")
   182  	}
   183  
   184  	if c.killed() {
   185  		t.Fatal("process failed to exit gracefully")
   186  	}
   187  }
   188  
   189  func TestClient_grpc_servercrash(t *testing.T) {
   190  	process := helperProcess("test-grpc")
   191  	c := NewClient(&ClientConfig{
   192  		Cmd:              process,
   193  		HandshakeConfig:  testHandshake,
   194  		Plugins:          testGRPCPluginMap,
   195  		AllowedProtocols: []Protocol{ProtocolGRPC},
   196  	})
   197  	defer c.Kill()
   198  
   199  	if _, err := c.Start(); err != nil {
   200  		t.Fatalf("err: %s", err)
   201  	}
   202  
   203  	if v := c.Protocol(); v != ProtocolGRPC {
   204  		t.Fatalf("bad: %s", v)
   205  	}
   206  
   207  	// Grab the RPC client
   208  	client, err := c.Client()
   209  	if err != nil {
   210  		t.Fatalf("err should be nil, got %s", err)
   211  	}
   212  
   213  	// Grab the impl
   214  	raw, err := client.Dispense("test")
   215  	if err != nil {
   216  		t.Fatalf("err should be nil, got %s", err)
   217  	}
   218  
   219  	_, ok := raw.(testInterface)
   220  	if !ok {
   221  		t.Fatalf("bad: %#v", raw)
   222  	}
   223  
   224  	c.runner.Kill(context.Background())
   225  
   226  	select {
   227  	case <-c.doneCtx.Done():
   228  	case <-time.After(time.Second * 2):
   229  		t.Fatal("Context was not closed")
   230  	}
   231  }
   232  
   233  func TestClient_grpc(t *testing.T) {
   234  	process := helperProcess("test-grpc")
   235  	c := NewClient(&ClientConfig{
   236  		Cmd:              process,
   237  		HandshakeConfig:  testHandshake,
   238  		Plugins:          testGRPCPluginMap,
   239  		AllowedProtocols: []Protocol{ProtocolGRPC},
   240  	})
   241  	defer c.Kill()
   242  
   243  	if _, err := c.Start(); err != nil {
   244  		t.Fatalf("err: %s", err)
   245  	}
   246  
   247  	if v := c.Protocol(); v != ProtocolGRPC {
   248  		t.Fatalf("bad: %s", v)
   249  	}
   250  
   251  	// Grab the RPC client
   252  	client, err := c.Client()
   253  	if err != nil {
   254  		t.Fatalf("err should be nil, got %s", err)
   255  	}
   256  
   257  	// Grab the impl
   258  	raw, err := client.Dispense("test")
   259  	if err != nil {
   260  		t.Fatalf("err should be nil, got %s", err)
   261  	}
   262  
   263  	impl, ok := raw.(testInterface)
   264  	if !ok {
   265  		t.Fatalf("bad: %#v", raw)
   266  	}
   267  
   268  	result := impl.Double(21)
   269  	if result != 42 {
   270  		t.Fatalf("bad: %#v", result)
   271  	}
   272  
   273  	// Kill it
   274  	c.Kill()
   275  
   276  	// Test that it knows it is exited
   277  	if !c.Exited() {
   278  		t.Fatal("should say client has exited")
   279  	}
   280  
   281  	if c.killed() {
   282  		t.Fatal("process failed to exit gracefully")
   283  	}
   284  }
   285  
   286  func TestClient_grpcNotAllowed(t *testing.T) {
   287  	process := helperProcess("test-grpc")
   288  	c := NewClient(&ClientConfig{
   289  		Cmd:             process,
   290  		HandshakeConfig: testHandshake,
   291  		Plugins:         testPluginMap,
   292  	})
   293  	defer c.Kill()
   294  
   295  	if _, err := c.Start(); err == nil {
   296  		t.Fatal("should error")
   297  	}
   298  }
   299  
   300  func TestClient_grpcSyncStdio(t *testing.T) {
   301  	for name, tc := range map[string]struct {
   302  		useRunnerFunc bool
   303  	}{
   304  		"default":        {false},
   305  		"use RunnerFunc": {true},
   306  	} {
   307  		t.Run(name, func(t *testing.T) {
   308  			testClient_grpcSyncStdio(t, tc.useRunnerFunc)
   309  		})
   310  	}
   311  }
   312  
   313  func testClient_grpcSyncStdio(t *testing.T, useRunnerFunc bool) {
   314  	var syncOut, syncErr safeBuffer
   315  
   316  	process := helperProcess("test-grpc")
   317  	cfg := &ClientConfig{
   318  		HandshakeConfig:  testHandshake,
   319  		Plugins:          testGRPCPluginMap,
   320  		AllowedProtocols: []Protocol{ProtocolGRPC},
   321  		SyncStdout:       &syncOut,
   322  		SyncStderr:       &syncErr,
   323  	}
   324  
   325  	if useRunnerFunc {
   326  		cfg.RunnerFunc = func(l hclog.Logger, cmd *exec.Cmd, _ string) (runner.Runner, error) {
   327  			process.Env = append(process.Env, cmd.Env...)
   328  			return cmdrunner.NewCmdRunner(l, process)
   329  		}
   330  	} else {
   331  		cfg.Cmd = process
   332  	}
   333  	c := NewClient(cfg)
   334  	defer c.Kill()
   335  
   336  	if _, err := c.Start(); err != nil {
   337  		t.Fatalf("err: %s", err)
   338  	}
   339  
   340  	if v := c.Protocol(); v != ProtocolGRPC {
   341  		t.Fatalf("bad: %s", v)
   342  	}
   343  
   344  	// Grab the RPC client
   345  	client, err := c.Client()
   346  	if err != nil {
   347  		t.Fatalf("err should be nil, got %s", err)
   348  	}
   349  
   350  	// Grab the impl
   351  	raw, err := client.Dispense("test")
   352  	if err != nil {
   353  		t.Fatalf("err should be nil, got %s", err)
   354  	}
   355  
   356  	impl, ok := raw.(testInterface)
   357  	if !ok {
   358  		t.Fatalf("bad: %#v", raw)
   359  	}
   360  
   361  	// Check reattach config is sensible.
   362  	reattach := c.ReattachConfig()
   363  	if useRunnerFunc {
   364  		if reattach.Pid != 0 {
   365  			t.Fatal(reattach.Pid)
   366  		}
   367  	} else {
   368  		if reattach.Pid == 0 {
   369  			t.Fatal(reattach.Pid)
   370  		}
   371  	}
   372  
   373  	// Print the data
   374  	stdout := []byte("hello\nworld!")
   375  	stderr := []byte("and some error\n messages!")
   376  	impl.PrintStdio(stdout, stderr)
   377  
   378  	// Wait for it to be copied
   379  	for syncOut.String() == "" || syncErr.String() == "" {
   380  		time.Sleep(10 * time.Millisecond)
   381  	}
   382  
   383  	// We should get the data
   384  	if syncOut.String() != string(stdout) {
   385  		t.Fatalf("stdout didn't match: %s", syncOut.String())
   386  	}
   387  	if syncErr.String() != string(stderr) {
   388  		t.Fatalf("stderr didn't match: %s", syncErr.String())
   389  	}
   390  }
   391  
   392  func TestClient_cmdAndReattach(t *testing.T) {
   393  	config := &ClientConfig{
   394  		Cmd:      helperProcess("start-timeout"),
   395  		Reattach: &ReattachConfig{},
   396  	}
   397  
   398  	c := NewClient(config)
   399  	defer c.Kill()
   400  
   401  	_, err := c.Start()
   402  	if err == nil {
   403  		t.Fatal("err should not be nil")
   404  	}
   405  }
   406  
   407  func TestClient_reattach(t *testing.T) {
   408  	process := helperProcess("test-interface")
   409  	c := NewClient(&ClientConfig{
   410  		Cmd:             process,
   411  		HandshakeConfig: testHandshake,
   412  		Plugins:         testPluginMap,
   413  	})
   414  	defer c.Kill()
   415  
   416  	// Grab the RPC client
   417  	_, err := c.Client()
   418  	if err != nil {
   419  		t.Fatalf("err should be nil, got %s", err)
   420  	}
   421  
   422  	// Get the reattach configuration
   423  	reattach := c.ReattachConfig()
   424  
   425  	// Create a new client
   426  	c = NewClient(&ClientConfig{
   427  		Reattach:        reattach,
   428  		HandshakeConfig: testHandshake,
   429  		Plugins:         testPluginMap,
   430  	})
   431  
   432  	// Grab the RPC client
   433  	client, err := c.Client()
   434  	if err != nil {
   435  		t.Fatalf("err should be nil, got %s", err)
   436  	}
   437  
   438  	// Grab the impl
   439  	raw, err := client.Dispense("test")
   440  	if err != nil {
   441  		t.Fatalf("err should be nil, got %s", err)
   442  	}
   443  
   444  	impl, ok := raw.(testInterface)
   445  	if !ok {
   446  		t.Fatalf("bad: %#v", raw)
   447  	}
   448  
   449  	result := impl.Double(21)
   450  	if result != 42 {
   451  		t.Fatalf("bad: %#v", result)
   452  	}
   453  
   454  	// Kill it
   455  	c.Kill()
   456  
   457  	// Test that it knows it is exited
   458  	if !c.Exited() {
   459  		t.Fatal("should say client has exited")
   460  	}
   461  
   462  	if c.killed() {
   463  		t.Fatal("process failed to exit gracefully")
   464  	}
   465  }
   466  
   467  func TestClient_reattachNoProtocol(t *testing.T) {
   468  	process := helperProcess("test-interface")
   469  	c := NewClient(&ClientConfig{
   470  		Cmd:             process,
   471  		HandshakeConfig: testHandshake,
   472  		Plugins:         testPluginMap,
   473  	})
   474  	defer c.Kill()
   475  
   476  	// Grab the RPC client
   477  	_, err := c.Client()
   478  	if err != nil {
   479  		t.Fatalf("err should be nil, got %s", err)
   480  	}
   481  
   482  	// Get the reattach configuration
   483  	reattach := c.ReattachConfig()
   484  	reattach.Protocol = ""
   485  
   486  	// Create a new client
   487  	c = NewClient(&ClientConfig{
   488  		Reattach:        reattach,
   489  		HandshakeConfig: testHandshake,
   490  		Plugins:         testPluginMap,
   491  	})
   492  
   493  	// Grab the RPC client
   494  	client, err := c.Client()
   495  	if err != nil {
   496  		t.Fatalf("err should be nil, got %s", err)
   497  	}
   498  
   499  	// Grab the impl
   500  	raw, err := client.Dispense("test")
   501  	if err != nil {
   502  		t.Fatalf("err should be nil, got %s", err)
   503  	}
   504  
   505  	impl, ok := raw.(testInterface)
   506  	if !ok {
   507  		t.Fatalf("bad: %#v", raw)
   508  	}
   509  
   510  	result := impl.Double(21)
   511  	if result != 42 {
   512  		t.Fatalf("bad: %#v", result)
   513  	}
   514  
   515  	// Kill it
   516  	c.Kill()
   517  
   518  	// Test that it knows it is exited
   519  	if !c.Exited() {
   520  		t.Fatal("should say client has exited")
   521  	}
   522  
   523  	if c.killed() {
   524  		t.Fatal("process failed to exit gracefully")
   525  	}
   526  }
   527  
   528  func TestClient_reattachGRPC(t *testing.T) {
   529  	for name, tc := range map[string]struct {
   530  		useReattachFunc bool
   531  	}{
   532  		"default":          {false},
   533  		"use ReattachFunc": {true},
   534  	} {
   535  		t.Run(name, func(t *testing.T) {
   536  			testClient_reattachGRPC(t, tc.useReattachFunc)
   537  		})
   538  	}
   539  }
   540  
   541  func testClient_reattachGRPC(t *testing.T, useReattachFunc bool) {
   542  	process := helperProcess("test-grpc")
   543  	c := NewClient(&ClientConfig{
   544  		Cmd:              process,
   545  		HandshakeConfig:  testHandshake,
   546  		Plugins:          testGRPCPluginMap,
   547  		AllowedProtocols: []Protocol{ProtocolGRPC},
   548  	})
   549  	defer c.Kill()
   550  
   551  	// Grab the RPC client
   552  	_, err := c.Client()
   553  	if err != nil {
   554  		t.Fatalf("err should be nil, got %s", err)
   555  	}
   556  
   557  	// Get the reattach configuration
   558  	reattach := c.ReattachConfig()
   559  
   560  	if useReattachFunc {
   561  		pid := reattach.Pid
   562  		reattach.Pid = 0
   563  		reattach.ReattachFunc = cmdrunner.ReattachFunc(pid, reattach.Addr)
   564  	}
   565  
   566  	// Create a new client
   567  	c = NewClient(&ClientConfig{
   568  		Reattach:         reattach,
   569  		HandshakeConfig:  testHandshake,
   570  		Plugins:          testGRPCPluginMap,
   571  		AllowedProtocols: []Protocol{ProtocolGRPC},
   572  	})
   573  
   574  	// Grab the RPC client
   575  	client, err := c.Client()
   576  	if err != nil {
   577  		t.Fatalf("err should be nil, got %s", err)
   578  	}
   579  
   580  	// Grab the impl
   581  	raw, err := client.Dispense("test")
   582  	if err != nil {
   583  		t.Fatalf("err should be nil, got %s", err)
   584  	}
   585  
   586  	impl, ok := raw.(testInterface)
   587  	if !ok {
   588  		t.Fatalf("bad: %#v", raw)
   589  	}
   590  
   591  	result := impl.Double(21)
   592  	if result != 42 {
   593  		t.Fatalf("bad: %#v", result)
   594  	}
   595  
   596  	// Kill it
   597  	c.Kill()
   598  
   599  	// Test that it knows it is exited
   600  	if !c.Exited() {
   601  		t.Fatal("should say client has exited")
   602  	}
   603  
   604  	if c.killed() {
   605  		t.Fatal("process failed to exit gracefully")
   606  	}
   607  }
   608  
   609  func TestClient_reattachNotFound(t *testing.T) {
   610  	// Find a bad pid
   611  	var pid int = 5000
   612  	for i := pid; i < 32000; i++ {
   613  		if _, err := os.FindProcess(i); err != nil {
   614  			pid = i
   615  			break
   616  		}
   617  	}
   618  
   619  	// Addr that won't work
   620  	l, err := net.Listen("tcp", "127.0.0.1:0")
   621  	if err != nil {
   622  		t.Fatalf("err: %s", err)
   623  	}
   624  	addr := l.Addr()
   625  	l.Close()
   626  
   627  	// Reattach
   628  	c := NewClient(&ClientConfig{
   629  		Reattach: &ReattachConfig{
   630  			Addr: addr,
   631  			Pid:  pid,
   632  		},
   633  		HandshakeConfig: testHandshake,
   634  		Plugins:         testPluginMap,
   635  	})
   636  
   637  	if _, err := c.Start(); err == nil {
   638  		t.Fatal("should error")
   639  	} else if err != ErrProcessNotFound {
   640  		t.Fatalf("err: %s", err)
   641  	}
   642  }
   643  
   644  func TestClientStart_badVersion(t *testing.T) {
   645  	config := &ClientConfig{
   646  		Cmd:             helperProcess("bad-version"),
   647  		StartTimeout:    50 * time.Millisecond,
   648  		HandshakeConfig: testHandshake,
   649  		Plugins:         testPluginMap,
   650  	}
   651  
   652  	c := NewClient(config)
   653  	defer c.Kill()
   654  
   655  	_, err := c.Start()
   656  	if err == nil {
   657  		t.Fatal("err should not be nil")
   658  	}
   659  }
   660  
   661  func TestClientStart_badNegotiatedVersion(t *testing.T) {
   662  	config := &ClientConfig{
   663  		Cmd:          helperProcess("test-versioned-plugins"),
   664  		StartTimeout: 50 * time.Millisecond,
   665  		// test-versioned-plugins only has version 2
   666  		HandshakeConfig: testHandshake,
   667  		Plugins:         testPluginMap,
   668  	}
   669  
   670  	c := NewClient(config)
   671  	defer c.Kill()
   672  
   673  	_, err := c.Start()
   674  	if err == nil {
   675  		t.Fatal("err should not be nil")
   676  	}
   677  	fmt.Println(err)
   678  }
   679  
   680  func TestClient_Start_Timeout(t *testing.T) {
   681  	config := &ClientConfig{
   682  		Cmd:             helperProcess("start-timeout"),
   683  		StartTimeout:    50 * time.Millisecond,
   684  		HandshakeConfig: testHandshake,
   685  		Plugins:         testPluginMap,
   686  	}
   687  
   688  	c := NewClient(config)
   689  	defer c.Kill()
   690  
   691  	_, err := c.Start()
   692  	if err == nil {
   693  		t.Fatal("err should not be nil")
   694  	}
   695  }
   696  
   697  func TestClient_Stderr(t *testing.T) {
   698  	stderr := new(bytes.Buffer)
   699  	process := helperProcess("stderr")
   700  	c := NewClient(&ClientConfig{
   701  		Cmd:             process,
   702  		Stderr:          stderr,
   703  		HandshakeConfig: testHandshake,
   704  		Plugins:         testPluginMap,
   705  	})
   706  	defer c.Kill()
   707  
   708  	if _, err := c.Start(); err != nil {
   709  		t.Fatalf("err: %s", err)
   710  	}
   711  
   712  	for !c.Exited() {
   713  		time.Sleep(10 * time.Millisecond)
   714  	}
   715  
   716  	if c.killed() {
   717  		t.Fatal("process failed to exit gracefully")
   718  	}
   719  
   720  	if !strings.Contains(stderr.String(), "HELLO\n") {
   721  		t.Fatalf("bad log data: '%s'", stderr.String())
   722  	}
   723  
   724  	if !strings.Contains(stderr.String(), "WORLD\n") {
   725  		t.Fatalf("bad log data: '%s'", stderr.String())
   726  	}
   727  }
   728  
   729  func TestClient_StderrJSON(t *testing.T) {
   730  	stderr := new(bytes.Buffer)
   731  	process := helperProcess("stderr-json")
   732  
   733  	var logBuf bytes.Buffer
   734  	mutex := new(sync.Mutex)
   735  	// Custom hclog.Logger
   736  	testLogger := hclog.New(&hclog.LoggerOptions{
   737  		Name:   "test-logger",
   738  		Level:  hclog.Trace,
   739  		Output: &logBuf,
   740  		Mutex:  mutex,
   741  	})
   742  
   743  	c := NewClient(&ClientConfig{
   744  		Cmd:             process,
   745  		Stderr:          stderr,
   746  		HandshakeConfig: testHandshake,
   747  		Logger:          testLogger,
   748  		Plugins:         testPluginMap,
   749  	})
   750  	defer c.Kill()
   751  
   752  	if _, err := c.Start(); err != nil {
   753  		t.Fatalf("err: %s", err)
   754  	}
   755  
   756  	for !c.Exited() {
   757  		time.Sleep(10 * time.Millisecond)
   758  	}
   759  
   760  	if c.killed() {
   761  		t.Fatal("process failed to exit gracefully")
   762  	}
   763  
   764  	logOut := logBuf.String()
   765  
   766  	if !strings.Contains(logOut, "[\"HELLO\"]\n") {
   767  		t.Fatalf("missing json list: '%s'", logOut)
   768  	}
   769  
   770  	if !strings.Contains(logOut, "12345\n") {
   771  		t.Fatalf("missing line with raw number: '%s'", logOut)
   772  	}
   773  
   774  	if !strings.Contains(logOut, "{\"a\":1}") {
   775  		t.Fatalf("missing json object: '%s'", logOut)
   776  	}
   777  }
   778  
   779  func TestClient_textLogLevel(t *testing.T) {
   780  	stderr := new(bytes.Buffer)
   781  	process := helperProcess("level-warn-text")
   782  
   783  	var logBuf bytes.Buffer
   784  	mutex := new(sync.Mutex)
   785  	// Custom hclog.Logger
   786  	testLogger := hclog.New(&hclog.LoggerOptions{
   787  		Name:   "test-logger",
   788  		Level:  hclog.Warn,
   789  		Output: &logBuf,
   790  		Mutex:  mutex,
   791  	})
   792  
   793  	c := NewClient(&ClientConfig{
   794  		Cmd:             process,
   795  		Stderr:          stderr,
   796  		HandshakeConfig: testHandshake,
   797  		Logger:          testLogger,
   798  		Plugins:         testPluginMap,
   799  	})
   800  	defer c.Kill()
   801  
   802  	if _, err := c.Start(); err != nil {
   803  		t.Fatalf("err: %s", err)
   804  	}
   805  
   806  	for !c.Exited() {
   807  		time.Sleep(10 * time.Millisecond)
   808  	}
   809  
   810  	if c.killed() {
   811  		t.Fatal("process failed to exit gracefully")
   812  	}
   813  
   814  	logOut := logBuf.String()
   815  
   816  	if !strings.Contains(logOut, "test line 98765") {
   817  		log.Fatalf("test string not found in log: %q\n", logOut)
   818  	}
   819  }
   820  
   821  func TestClient_Stdin(t *testing.T) {
   822  	// Overwrite stdin for this test with a temporary file
   823  	tf, err := os.CreateTemp("", "terraform")
   824  	if err != nil {
   825  		t.Fatalf("err: %s", err)
   826  	}
   827  	defer os.Remove(tf.Name())
   828  	defer tf.Close()
   829  
   830  	if _, err = tf.WriteString("hello"); err != nil {
   831  		t.Fatalf("error: %s", err)
   832  	}
   833  
   834  	if err = tf.Sync(); err != nil {
   835  		t.Fatalf("error: %s", err)
   836  	}
   837  
   838  	if _, err = tf.Seek(0, 0); err != nil {
   839  		t.Fatalf("error: %s", err)
   840  	}
   841  
   842  	oldStdin := os.Stdin
   843  	defer func() { os.Stdin = oldStdin }()
   844  	os.Stdin = tf
   845  
   846  	process := helperProcess("stdin")
   847  	c := NewClient(&ClientConfig{
   848  		Cmd:             process,
   849  		HandshakeConfig: testHandshake,
   850  		Plugins:         testPluginMap,
   851  	})
   852  	defer c.Kill()
   853  
   854  	_, err = c.Start()
   855  	if err != nil {
   856  		t.Fatalf("error: %s", err)
   857  	}
   858  
   859  	for {
   860  		if c.Exited() {
   861  			break
   862  		}
   863  
   864  		time.Sleep(50 * time.Millisecond)
   865  	}
   866  
   867  	if !process.ProcessState.Success() {
   868  		t.Fatal("process didn't exit cleanly")
   869  	}
   870  }
   871  
   872  func TestClient_SkipHostEnv(t *testing.T) {
   873  	for _, tc := range []struct {
   874  		helper string
   875  		skip   bool
   876  	}{
   877  		{"test-skip-host-env-true", true},
   878  		{"test-skip-host-env-false", false},
   879  	} {
   880  		t.Run(tc.helper, func(t *testing.T) {
   881  			process := helperProcess(tc.helper)
   882  			// Set env in the host process, which we'll look for in the plugin.
   883  			t.Setenv("PLUGIN_TEST_SKIP_HOST_ENV", "foo")
   884  			c := NewClient(&ClientConfig{
   885  				Cmd:             process,
   886  				HandshakeConfig: testHandshake,
   887  				Plugins:         testPluginMap,
   888  				SkipHostEnv:     tc.skip,
   889  			})
   890  			defer c.Kill()
   891  
   892  			_, err := c.Start()
   893  			if err != nil {
   894  				t.Fatalf("error: %s", err)
   895  			}
   896  
   897  			for {
   898  				if c.Exited() {
   899  					break
   900  				}
   901  
   902  				time.Sleep(50 * time.Millisecond)
   903  			}
   904  
   905  			if !process.ProcessState.Success() {
   906  				t.Fatal("process didn't exit cleanly")
   907  			}
   908  		})
   909  	}
   910  }
   911  
   912  func TestClient_RequestGRPCMultiplexing_UnsupportedByPlugin(t *testing.T) {
   913  	for _, name := range []string{
   914  		"mux-grpc-with-old-plugin",
   915  		"mux-grpc-with-unsupported-plugin",
   916  	} {
   917  		t.Run(name, func(t *testing.T) {
   918  			process := helperProcess(name)
   919  			c := NewClient(&ClientConfig{
   920  				Cmd:                 process,
   921  				HandshakeConfig:     testHandshake,
   922  				Plugins:             testGRPCPluginMap,
   923  				AllowedProtocols:    []Protocol{ProtocolGRPC},
   924  				GRPCBrokerMultiplex: true,
   925  			})
   926  			defer c.Kill()
   927  
   928  			_, err := c.Start()
   929  			if err == nil {
   930  				t.Fatal("expected error")
   931  			}
   932  
   933  			if !errors.Is(err, ErrGRPCBrokerMuxNotSupported) {
   934  				t.Fatalf("expected %s, but got %s", ErrGRPCBrokerMuxNotSupported, err)
   935  			}
   936  		})
   937  	}
   938  }
   939  
   940  func TestClient_SecureConfig(t *testing.T) {
   941  	// Test failure case
   942  	secureConfig := &SecureConfig{
   943  		Checksum: []byte{'1'},
   944  		Hash:     sha256.New(),
   945  	}
   946  	process := helperProcess("test-interface")
   947  	c := NewClient(&ClientConfig{
   948  		Cmd:             process,
   949  		HandshakeConfig: testHandshake,
   950  		Plugins:         testPluginMap,
   951  		SecureConfig:    secureConfig,
   952  	})
   953  
   954  	// Grab the RPC client, should error
   955  	_, err := c.Client()
   956  	c.Kill()
   957  	if err != ErrChecksumsDoNotMatch {
   958  		t.Fatalf("err should be %s, got %s", ErrChecksumsDoNotMatch, err)
   959  	}
   960  
   961  	// Get the checksum of the executable
   962  	file, err := os.Open(os.Args[0])
   963  	if err != nil {
   964  		t.Fatal(err)
   965  	}
   966  	defer file.Close()
   967  
   968  	hash := sha256.New()
   969  
   970  	_, err = io.Copy(hash, file)
   971  	if err != nil {
   972  		t.Fatal(err)
   973  	}
   974  
   975  	sum := hash.Sum(nil)
   976  
   977  	secureConfig = &SecureConfig{
   978  		Checksum: sum,
   979  		Hash:     sha256.New(),
   980  	}
   981  
   982  	process = helperProcess("test-interface")
   983  	c = NewClient(&ClientConfig{
   984  		Cmd:             process,
   985  		HandshakeConfig: testHandshake,
   986  		Plugins:         testPluginMap,
   987  		SecureConfig:    secureConfig,
   988  	})
   989  	defer c.Kill()
   990  
   991  	// Grab the RPC client
   992  	_, err = c.Client()
   993  	if err != nil {
   994  		t.Fatalf("err should be nil, got %s", err)
   995  	}
   996  }
   997  
   998  func TestClient_TLS(t *testing.T) {
   999  	// Test failure case
  1000  	process := helperProcess("test-interface-tls")
  1001  	cBad := NewClient(&ClientConfig{
  1002  		Cmd:             process,
  1003  		HandshakeConfig: testHandshake,
  1004  		Plugins:         testPluginMap,
  1005  	})
  1006  	defer cBad.Kill()
  1007  
  1008  	// Grab the RPC client
  1009  	clientBad, err := cBad.Client()
  1010  	if err != nil {
  1011  		t.Fatalf("err should be nil, got %s", err)
  1012  	}
  1013  
  1014  	// Grab the impl
  1015  	raw, err := clientBad.Dispense("test")
  1016  	if err == nil {
  1017  		t.Fatal("expected error, got nil")
  1018  	}
  1019  
  1020  	cBad.Kill()
  1021  
  1022  	// Add TLS config to client
  1023  	tlsConfig, err := helperTLSProvider()
  1024  	if err != nil {
  1025  		t.Fatalf("err should be nil, got %s", err)
  1026  	}
  1027  
  1028  	process = helperProcess("test-interface-tls")
  1029  	c := NewClient(&ClientConfig{
  1030  		Cmd:             process,
  1031  		HandshakeConfig: testHandshake,
  1032  		Plugins:         testPluginMap,
  1033  		TLSConfig:       tlsConfig,
  1034  	})
  1035  	defer c.Kill()
  1036  
  1037  	// Grab the RPC client
  1038  	client, err := c.Client()
  1039  	if err != nil {
  1040  		t.Fatalf("err should be nil, got %s", err)
  1041  	}
  1042  
  1043  	// Grab the impl
  1044  	raw, err = client.Dispense("test")
  1045  	if err != nil {
  1046  		t.Fatalf("err should be nil, got %s", err)
  1047  	}
  1048  
  1049  	impl, ok := raw.(testInterface)
  1050  	if !ok {
  1051  		t.Fatalf("bad: %#v", raw)
  1052  	}
  1053  
  1054  	result := impl.Double(21)
  1055  	if result != 42 {
  1056  		t.Fatalf("bad: %#v", result)
  1057  	}
  1058  
  1059  	// Kill it
  1060  	c.Kill()
  1061  
  1062  	// Test that it knows it is exited
  1063  	if !c.Exited() {
  1064  		t.Fatal("should say client has exited")
  1065  	}
  1066  
  1067  	if c.killed() {
  1068  		t.Fatal("process failed to exit gracefully")
  1069  	}
  1070  }
  1071  
  1072  func TestClient_TLS_grpc(t *testing.T) {
  1073  	// Add TLS config to client
  1074  	tlsConfig, err := helperTLSProvider()
  1075  	if err != nil {
  1076  		t.Fatalf("err should be nil, got %s", err)
  1077  	}
  1078  
  1079  	process := helperProcess("test-grpc-tls")
  1080  	c := NewClient(&ClientConfig{
  1081  		Cmd:              process,
  1082  		HandshakeConfig:  testHandshake,
  1083  		Plugins:          testGRPCPluginMap,
  1084  		TLSConfig:        tlsConfig,
  1085  		AllowedProtocols: []Protocol{ProtocolGRPC},
  1086  	})
  1087  	defer c.Kill()
  1088  
  1089  	// Grab the RPC client
  1090  	client, err := c.Client()
  1091  	if err != nil {
  1092  		t.Fatalf("err should be nil, got %s", err)
  1093  	}
  1094  
  1095  	// Grab the impl
  1096  	raw, err := client.Dispense("test")
  1097  	if err != nil {
  1098  		t.Fatalf("err should be nil, got %s", err)
  1099  	}
  1100  
  1101  	impl, ok := raw.(testInterface)
  1102  	if !ok {
  1103  		t.Fatalf("bad: %#v", raw)
  1104  	}
  1105  
  1106  	result := impl.Double(21)
  1107  	if result != 42 {
  1108  		t.Fatalf("bad: %#v", result)
  1109  	}
  1110  
  1111  	// Kill it
  1112  	c.Kill()
  1113  
  1114  	if !c.Exited() {
  1115  		t.Fatal("should say client has exited")
  1116  	}
  1117  
  1118  	if c.killed() {
  1119  		t.Fatal("process failed to exit gracefully")
  1120  	}
  1121  }
  1122  
  1123  func TestClient_secureConfigAndReattach(t *testing.T) {
  1124  	config := &ClientConfig{
  1125  		SecureConfig: &SecureConfig{},
  1126  		Reattach:     &ReattachConfig{},
  1127  	}
  1128  
  1129  	c := NewClient(config)
  1130  	defer c.Kill()
  1131  
  1132  	_, err := c.Start()
  1133  	if err != ErrSecureConfigAndReattach {
  1134  		t.Fatalf("err should not be %s, got %s", ErrSecureConfigAndReattach, err)
  1135  	}
  1136  }
  1137  
  1138  func TestClient_ping(t *testing.T) {
  1139  	process := helperProcess("test-interface")
  1140  	c := NewClient(&ClientConfig{
  1141  		Cmd:             process,
  1142  		HandshakeConfig: testHandshake,
  1143  		Plugins:         testPluginMap,
  1144  	})
  1145  	defer c.Kill()
  1146  
  1147  	// Get the client
  1148  	client, err := c.Client()
  1149  	if err != nil {
  1150  		t.Fatalf("err: %s", err)
  1151  	}
  1152  
  1153  	// Ping, should work
  1154  	if err := client.Ping(); err != nil {
  1155  		t.Fatalf("err: %s", err)
  1156  	}
  1157  
  1158  	// Kill it
  1159  	c.Kill()
  1160  	if err := client.Ping(); err == nil {
  1161  		t.Fatal("should error")
  1162  	}
  1163  }
  1164  
  1165  func TestClient_wrongVersion(t *testing.T) {
  1166  	process := helperProcess("test-proto-upgraded-plugin")
  1167  	c := NewClient(&ClientConfig{
  1168  		Cmd:              process,
  1169  		HandshakeConfig:  testHandshake,
  1170  		Plugins:          testGRPCPluginMap,
  1171  		AllowedProtocols: []Protocol{ProtocolGRPC},
  1172  	})
  1173  	defer c.Kill()
  1174  
  1175  	// Get the client
  1176  	_, err := c.Client()
  1177  	if err == nil {
  1178  		t.Fatal("expected incorrect protocol version server")
  1179  	}
  1180  
  1181  }
  1182  
  1183  func TestClient_legacyClient(t *testing.T) {
  1184  	process := helperProcess("test-proto-upgraded-plugin")
  1185  	c := NewClient(&ClientConfig{
  1186  		Cmd:             process,
  1187  		HandshakeConfig: testVersionedHandshake,
  1188  		VersionedPlugins: map[int]PluginSet{
  1189  			1: testPluginMap,
  1190  		},
  1191  	})
  1192  	defer c.Kill()
  1193  
  1194  	// Get the client
  1195  	client, err := c.Client()
  1196  	if err != nil {
  1197  		t.Fatalf("err: %s", err)
  1198  	}
  1199  
  1200  	if c.NegotiatedVersion() != 1 {
  1201  		t.Fatal("using incorrect version", c.NegotiatedVersion())
  1202  	}
  1203  
  1204  	// Ping, should work
  1205  	if err := client.Ping(); err == nil {
  1206  		t.Fatal("expected error, should negotiate wrong plugin")
  1207  	}
  1208  }
  1209  
  1210  func TestClient_legacyServer(t *testing.T) {
  1211  	// test using versioned plugins version when the server supports only
  1212  	// supports one
  1213  	process := helperProcess("test-proto-upgraded-client")
  1214  	c := NewClient(&ClientConfig{
  1215  		Cmd:             process,
  1216  		HandshakeConfig: testVersionedHandshake,
  1217  		VersionedPlugins: map[int]PluginSet{
  1218  			2: testGRPCPluginMap,
  1219  		},
  1220  		AllowedProtocols: []Protocol{ProtocolGRPC},
  1221  	})
  1222  	defer c.Kill()
  1223  
  1224  	// Get the client
  1225  	client, err := c.Client()
  1226  	if err != nil {
  1227  		t.Fatalf("err: %s", err)
  1228  	}
  1229  
  1230  	if c.NegotiatedVersion() != 2 {
  1231  		t.Fatal("using incorrect version", c.NegotiatedVersion())
  1232  	}
  1233  
  1234  	// Ping, should work
  1235  	if err := client.Ping(); err == nil {
  1236  		t.Fatal("expected error, should negotiate wrong plugin")
  1237  	}
  1238  }
  1239  
  1240  func TestClient_versionedClient(t *testing.T) {
  1241  	process := helperProcess("test-versioned-plugins")
  1242  	c := NewClient(&ClientConfig{
  1243  		Cmd:             process,
  1244  		HandshakeConfig: testVersionedHandshake,
  1245  		VersionedPlugins: map[int]PluginSet{
  1246  			2: testGRPCPluginMap,
  1247  		},
  1248  		AllowedProtocols: []Protocol{ProtocolGRPC},
  1249  	})
  1250  	defer c.Kill()
  1251  
  1252  	if _, err := c.Start(); err != nil {
  1253  		t.Fatalf("err: %s", err)
  1254  	}
  1255  
  1256  	if v := c.Protocol(); v != ProtocolGRPC {
  1257  		t.Fatalf("bad: %s", v)
  1258  	}
  1259  
  1260  	// Grab the RPC client
  1261  	client, err := c.Client()
  1262  	if err != nil {
  1263  		t.Fatalf("err should be nil, got %s", err)
  1264  	}
  1265  
  1266  	if c.NegotiatedVersion() != 2 {
  1267  		t.Fatal("using incorrect version", c.NegotiatedVersion())
  1268  	}
  1269  
  1270  	// Grab the impl
  1271  	raw, err := client.Dispense("test")
  1272  	if err != nil {
  1273  		t.Fatalf("err should be nil, got %s", err)
  1274  	}
  1275  
  1276  	_, ok := raw.(testInterface)
  1277  	if !ok {
  1278  		t.Fatalf("bad: %#v", raw)
  1279  	}
  1280  
  1281  	c.runner.Kill(context.Background())
  1282  
  1283  	select {
  1284  	case <-c.doneCtx.Done():
  1285  	case <-time.After(time.Second * 2):
  1286  		t.Fatal("Context was not closed")
  1287  	}
  1288  }
  1289  
  1290  func TestClient_mtlsClient(t *testing.T) {
  1291  	process := helperProcess("test-mtls")
  1292  	c := NewClient(&ClientConfig{
  1293  		AutoMTLS:        true,
  1294  		Cmd:             process,
  1295  		HandshakeConfig: testVersionedHandshake,
  1296  		VersionedPlugins: map[int]PluginSet{
  1297  			2: testGRPCPluginMap,
  1298  		},
  1299  		AllowedProtocols: []Protocol{ProtocolGRPC},
  1300  	})
  1301  	defer c.Kill()
  1302  
  1303  	if _, err := c.Start(); err != nil {
  1304  		t.Fatalf("err: %s", err)
  1305  	}
  1306  
  1307  	if v := c.Protocol(); v != ProtocolGRPC {
  1308  		t.Fatalf("bad: %s", v)
  1309  	}
  1310  
  1311  	// Grab the RPC client
  1312  	client, err := c.Client()
  1313  	if err != nil {
  1314  		t.Fatalf("err should be nil, got %s", err)
  1315  	}
  1316  
  1317  	if c.NegotiatedVersion() != 2 {
  1318  		t.Fatal("using incorrect version", c.NegotiatedVersion())
  1319  	}
  1320  
  1321  	// Grab the impl
  1322  	raw, err := client.Dispense("test")
  1323  	if err != nil {
  1324  		t.Fatalf("err should be nil, got %s", err)
  1325  	}
  1326  
  1327  	tester, ok := raw.(testInterface)
  1328  	if !ok {
  1329  		t.Fatalf("bad: %#v", raw)
  1330  	}
  1331  
  1332  	n := tester.Double(3)
  1333  	if n != 6 {
  1334  		t.Fatal("invalid response", n)
  1335  	}
  1336  
  1337  	c.runner.Kill(context.Background())
  1338  
  1339  	select {
  1340  	case <-c.doneCtx.Done():
  1341  	case <-time.After(time.Second * 2):
  1342  		t.Fatal("Context was not closed")
  1343  	}
  1344  }
  1345  
  1346  func TestClient_mtlsNetRPCClient(t *testing.T) {
  1347  	process := helperProcess("test-interface-mtls")
  1348  	c := NewClient(&ClientConfig{
  1349  		AutoMTLS:         true,
  1350  		Cmd:              process,
  1351  		HandshakeConfig:  testVersionedHandshake,
  1352  		Plugins:          testPluginMap,
  1353  		AllowedProtocols: []Protocol{ProtocolNetRPC},
  1354  	})
  1355  	defer c.Kill()
  1356  
  1357  	if _, err := c.Start(); err != nil {
  1358  		t.Fatalf("err: %s", err)
  1359  	}
  1360  
  1361  	// Grab the RPC client
  1362  	client, err := c.Client()
  1363  	if err != nil {
  1364  		t.Fatalf("err should be nil, got %s", err)
  1365  	}
  1366  
  1367  	// Grab the impl
  1368  	raw, err := client.Dispense("test")
  1369  	if err != nil {
  1370  		t.Fatalf("err should be nil, got %s", err)
  1371  	}
  1372  
  1373  	tester, ok := raw.(testInterface)
  1374  	if !ok {
  1375  		t.Fatalf("bad: %#v", raw)
  1376  	}
  1377  
  1378  	n := tester.Double(3)
  1379  	if n != 6 {
  1380  		t.Fatal("invalid response", n)
  1381  	}
  1382  
  1383  	c.runner.Kill(context.Background())
  1384  
  1385  	select {
  1386  	case <-c.doneCtx.Done():
  1387  	case <-time.After(time.Second * 2):
  1388  		t.Fatal("Context was not closed")
  1389  	}
  1390  }
  1391  
  1392  func TestClient_logger(t *testing.T) {
  1393  	t.Run("net/rpc", func(t *testing.T) { testClient_logger(t, "netrpc") })
  1394  	t.Run("grpc", func(t *testing.T) { testClient_logger(t, "grpc") })
  1395  }
  1396  
  1397  func testClient_logger(t *testing.T, proto string) {
  1398  	var buffer bytes.Buffer
  1399  	mutex := new(sync.Mutex)
  1400  	stderr := io.MultiWriter(os.Stderr, &buffer)
  1401  	// Custom hclog.Logger
  1402  	clientLogger := hclog.New(&hclog.LoggerOptions{
  1403  		Name:   "test-logger",
  1404  		Level:  hclog.Trace,
  1405  		Output: stderr,
  1406  		Mutex:  mutex,
  1407  	})
  1408  
  1409  	process := helperProcess("test-interface-logger-" + proto)
  1410  	c := NewClient(&ClientConfig{
  1411  		Cmd:              process,
  1412  		HandshakeConfig:  testHandshake,
  1413  		Plugins:          testGRPCPluginMap,
  1414  		Logger:           clientLogger,
  1415  		AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC},
  1416  	})
  1417  	defer c.Kill()
  1418  
  1419  	// Grab the RPC client
  1420  	client, err := c.Client()
  1421  	if err != nil {
  1422  		t.Fatalf("err should be nil, got %s", err)
  1423  	}
  1424  
  1425  	// Grab the impl
  1426  	raw, err := client.Dispense("test")
  1427  	if err != nil {
  1428  		t.Fatalf("err should be nil, got %s", err)
  1429  	}
  1430  
  1431  	impl, ok := raw.(testInterface)
  1432  	if !ok {
  1433  		t.Fatalf("bad: %#v", raw)
  1434  	}
  1435  
  1436  	{
  1437  		// Discard everything else, and capture the output we care about
  1438  		mutex.Lock()
  1439  		buffer.Reset()
  1440  		mutex.Unlock()
  1441  		impl.PrintKV("foo", "bar")
  1442  		time.Sleep(100 * time.Millisecond)
  1443  		mutex.Lock()
  1444  		line, err := buffer.ReadString('\n')
  1445  		mutex.Unlock()
  1446  		if err != nil {
  1447  			t.Fatal(err)
  1448  		}
  1449  		if !strings.Contains(line, "foo=bar") {
  1450  			t.Fatalf("bad: %q", line)
  1451  		}
  1452  	}
  1453  
  1454  	{
  1455  		// Try an integer type
  1456  		mutex.Lock()
  1457  		buffer.Reset()
  1458  		mutex.Unlock()
  1459  		impl.PrintKV("foo", 12)
  1460  		time.Sleep(100 * time.Millisecond)
  1461  		mutex.Lock()
  1462  		line, err := buffer.ReadString('\n')
  1463  		mutex.Unlock()
  1464  		if err != nil {
  1465  			t.Fatal(err)
  1466  		}
  1467  		if !strings.Contains(line, "foo=12") {
  1468  			t.Fatalf("bad: %q", line)
  1469  		}
  1470  	}
  1471  
  1472  	// Kill it
  1473  	c.Kill()
  1474  
  1475  	// Test that it knows it is exited
  1476  	if !c.Exited() {
  1477  		t.Fatal("should say client has exited")
  1478  	}
  1479  
  1480  	if c.killed() {
  1481  		t.Fatal("process failed to exit gracefully")
  1482  	}
  1483  }
  1484  
  1485  // Test that we continue to consume stderr over long lines.
  1486  func TestClient_logStderr(t *testing.T) {
  1487  	stderr := bytes.Buffer{}
  1488  	c := NewClient(&ClientConfig{
  1489  		Stderr: &stderr,
  1490  		Cmd: &exec.Cmd{
  1491  			Path: "test",
  1492  		},
  1493  		PluginLogBufferSize: 32,
  1494  	})
  1495  	c.clientWaitGroup.Add(1)
  1496  
  1497  	msg := `
  1498  this line is more than 32 bytes long
  1499  and this line is more than 32 bytes long
  1500  {"a": "b", "@level": "debug"}
  1501  this line is short
  1502  `
  1503  
  1504  	reader := strings.NewReader(msg)
  1505  
  1506  	c.stderrWaitGroup.Add(1)
  1507  	c.logStderr(c.config.Cmd.Path, reader)
  1508  	read := stderr.String()
  1509  
  1510  	if read != msg {
  1511  		t.Fatalf("\nexpected output: %q\ngot output:      %q", msg, read)
  1512  	}
  1513  }
  1514  
  1515  func TestClient_logStderrParseJSON(t *testing.T) {
  1516  	logBuf := bytes.Buffer{}
  1517  	c := NewClient(&ClientConfig{
  1518  		Stderr:              bytes.NewBuffer(nil),
  1519  		Cmd:                 &exec.Cmd{Path: "test"},
  1520  		PluginLogBufferSize: 64,
  1521  		Logger: hclog.New(&hclog.LoggerOptions{
  1522  			Name:       "test-logger",
  1523  			Level:      hclog.Trace,
  1524  			Output:     &logBuf,
  1525  			JSONFormat: true,
  1526  		}),
  1527  	})
  1528  	c.clientWaitGroup.Add(1)
  1529  
  1530  	msg := `{"@message": "this is a message", "@level": "info"}
  1531  {"@message": "this is a large message that is more than 64 bytes long", "@level": "info"}`
  1532  	reader := strings.NewReader(msg)
  1533  
  1534  	c.stderrWaitGroup.Add(1)
  1535  	c.logStderr(c.config.Cmd.Path, reader)
  1536  	logs := strings.Split(strings.TrimSpace(logBuf.String()), "\n")
  1537  
  1538  	wants := []struct {
  1539  		wantLevel   string
  1540  		wantMessage string
  1541  	}{
  1542  		{"info", "this is a message"},
  1543  		{"debug", `{"@message": "this is a large message that is more than 64 bytes`},
  1544  		{"debug", ` long", "@level": "info"}`},
  1545  	}
  1546  
  1547  	if len(logs) != len(wants) {
  1548  		t.Fatalf("expected %d logs, got %d", len(wants), len(logs))
  1549  	}
  1550  
  1551  	for i, tt := range wants {
  1552  		l := make(map[string]interface{})
  1553  		if err := json.Unmarshal([]byte(logs[i]), &l); err != nil {
  1554  			t.Fatal(err)
  1555  		}
  1556  
  1557  		if l["@level"] != tt.wantLevel {
  1558  			t.Fatalf("expected level %q, got %q", tt.wantLevel, l["@level"])
  1559  		}
  1560  
  1561  		if l["@message"] != tt.wantMessage {
  1562  			t.Fatalf("expected message %q, got %q", tt.wantMessage, l["@message"])
  1563  		}
  1564  	}
  1565  }