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