OSDN Git Service

add package
[bytom/vapor.git] / vendor / github.com / hashicorp / go-plugin / client_test.go
1 package plugin
2
3 import (
4         "bytes"
5         "crypto/sha256"
6         "io"
7         "io/ioutil"
8         "net"
9         "os"
10         "path/filepath"
11         "strings"
12         "sync"
13         "testing"
14         "time"
15
16         hclog "github.com/hashicorp/go-hclog"
17 )
18
19 func TestClient(t *testing.T) {
20         process := helperProcess("mock")
21         c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake})
22         defer c.Kill()
23
24         // Test that it parses the proper address
25         addr, err := c.Start()
26         if err != nil {
27                 t.Fatalf("err should be nil, got %s", err)
28         }
29
30         if addr.Network() != "tcp" {
31                 t.Fatalf("bad: %#v", addr)
32         }
33
34         if addr.String() != ":1234" {
35                 t.Fatalf("bad: %#v", addr)
36         }
37
38         // Test that it exits properly if killed
39         c.Kill()
40
41         if process.ProcessState == nil {
42                 t.Fatal("should have process state")
43         }
44
45         // Test that it knows it is exited
46         if !c.Exited() {
47                 t.Fatal("should say client has exited")
48         }
49 }
50
51 // This tests a bug where Kill would start
52 func TestClient_killStart(t *testing.T) {
53         // Create a temporary dir to store the result file
54         td, err := ioutil.TempDir("", "plugin")
55         if err != nil {
56                 t.Fatalf("err: %s", err)
57         }
58         defer os.RemoveAll(td)
59
60         // Start the client
61         path := filepath.Join(td, "booted")
62         process := helperProcess("bad-version", path)
63         c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake})
64         defer c.Kill()
65
66         // Verify our path doesn't exist
67         if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
68                 t.Fatalf("bad: %s", err)
69         }
70
71         // Test that it parses the proper address
72         if _, err := c.Start(); err == nil {
73                 t.Fatal("expected error")
74         }
75
76         // Verify we started
77         if _, err := os.Stat(path); err != nil {
78                 t.Fatalf("bad: %s", err)
79         }
80         if err := os.Remove(path); err != nil {
81                 t.Fatalf("bad: %s", err)
82         }
83
84         // Test that Kill does nothing really
85         c.Kill()
86
87         // Test that it knows it is exited
88         if !c.Exited() {
89                 t.Fatal("should say client has exited")
90         }
91
92         if process.ProcessState == nil {
93                 t.Fatal("should have no process state")
94         }
95
96         // Verify our path doesn't exist
97         if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
98                 t.Fatalf("bad: %s", err)
99         }
100 }
101
102 func TestClient_testCleanup(t *testing.T) {
103         // Create a temporary dir to store the result file
104         td, err := ioutil.TempDir("", "plugin")
105         if err != nil {
106                 t.Fatalf("err: %s", err)
107         }
108         defer os.RemoveAll(td)
109
110         // Create a path that the helper process will write on cleanup
111         path := filepath.Join(td, "output")
112
113         // Test the cleanup
114         process := helperProcess("cleanup", path)
115         c := NewClient(&ClientConfig{
116                 Cmd:             process,
117                 HandshakeConfig: testHandshake,
118                 Plugins:         testPluginMap,
119         })
120
121         // Grab the client so the process starts
122         if _, err := c.Client(); err != nil {
123                 c.Kill()
124                 t.Fatalf("err: %s", err)
125         }
126
127         // Kill it gracefully
128         c.Kill()
129
130         // Test for the file
131         if _, err := os.Stat(path); err != nil {
132                 t.Fatalf("err: %s", err)
133         }
134 }
135
136 func TestClient_testInterface(t *testing.T) {
137         process := helperProcess("test-interface")
138         c := NewClient(&ClientConfig{
139                 Cmd:             process,
140                 HandshakeConfig: testHandshake,
141                 Plugins:         testPluginMap,
142         })
143         defer c.Kill()
144
145         // Grab the RPC client
146         client, err := c.Client()
147         if err != nil {
148                 t.Fatalf("err should be nil, got %s", err)
149         }
150
151         // Grab the impl
152         raw, err := client.Dispense("test")
153         if err != nil {
154                 t.Fatalf("err should be nil, got %s", err)
155         }
156
157         impl, ok := raw.(testInterface)
158         if !ok {
159                 t.Fatalf("bad: %#v", raw)
160         }
161
162         result := impl.Double(21)
163         if result != 42 {
164                 t.Fatalf("bad: %#v", result)
165         }
166
167         // Kill it
168         c.Kill()
169
170         // Test that it knows it is exited
171         if !c.Exited() {
172                 t.Fatal("should say client has exited")
173         }
174 }
175
176 func TestClient_grpc_servercrash(t *testing.T) {
177         process := helperProcess("test-grpc")
178         c := NewClient(&ClientConfig{
179                 Cmd:              process,
180                 HandshakeConfig:  testHandshake,
181                 Plugins:          testPluginMap,
182                 AllowedProtocols: []Protocol{ProtocolGRPC},
183         })
184         defer c.Kill()
185
186         if _, err := c.Start(); err != nil {
187                 t.Fatalf("err: %s", err)
188         }
189
190         if v := c.Protocol(); v != ProtocolGRPC {
191                 t.Fatalf("bad: %s", v)
192         }
193
194         // Grab the RPC client
195         client, err := c.Client()
196         if err != nil {
197                 t.Fatalf("err should be nil, got %s", err)
198         }
199
200         // Grab the impl
201         raw, err := client.Dispense("test")
202         if err != nil {
203                 t.Fatalf("err should be nil, got %s", err)
204         }
205
206         _, ok := raw.(testInterface)
207         if !ok {
208                 t.Fatalf("bad: %#v", raw)
209         }
210
211         c.process.Kill()
212
213         select {
214         case <-c.doneCtx.Done():
215         case <-time.After(time.Second * 2):
216                 t.Fatal("Context was not closed")
217         }
218 }
219
220 func TestClient_grpc(t *testing.T) {
221         process := helperProcess("test-grpc")
222         c := NewClient(&ClientConfig{
223                 Cmd:              process,
224                 HandshakeConfig:  testHandshake,
225                 Plugins:          testPluginMap,
226                 AllowedProtocols: []Protocol{ProtocolGRPC},
227         })
228         defer c.Kill()
229
230         if _, err := c.Start(); err != nil {
231                 t.Fatalf("err: %s", err)
232         }
233
234         if v := c.Protocol(); v != ProtocolGRPC {
235                 t.Fatalf("bad: %s", v)
236         }
237
238         // Grab the RPC client
239         client, err := c.Client()
240         if err != nil {
241                 t.Fatalf("err should be nil, got %s", err)
242         }
243
244         // Grab the impl
245         raw, err := client.Dispense("test")
246         if err != nil {
247                 t.Fatalf("err should be nil, got %s", err)
248         }
249
250         impl, ok := raw.(testInterface)
251         if !ok {
252                 t.Fatalf("bad: %#v", raw)
253         }
254
255         result := impl.Double(21)
256         if result != 42 {
257                 t.Fatalf("bad: %#v", result)
258         }
259
260         // Kill it
261         c.Kill()
262
263         // Test that it knows it is exited
264         if !c.Exited() {
265                 t.Fatal("should say client has exited")
266         }
267 }
268
269 func TestClient_grpcNotAllowed(t *testing.T) {
270         process := helperProcess("test-grpc")
271         c := NewClient(&ClientConfig{
272                 Cmd:             process,
273                 HandshakeConfig: testHandshake,
274                 Plugins:         testPluginMap,
275         })
276         defer c.Kill()
277
278         if _, err := c.Start(); err == nil {
279                 t.Fatal("should error")
280         }
281 }
282
283 func TestClient_cmdAndReattach(t *testing.T) {
284         config := &ClientConfig{
285                 Cmd:      helperProcess("start-timeout"),
286                 Reattach: &ReattachConfig{},
287         }
288
289         c := NewClient(config)
290         defer c.Kill()
291
292         _, err := c.Start()
293         if err == nil {
294                 t.Fatal("err should not be nil")
295         }
296 }
297
298 func TestClient_reattach(t *testing.T) {
299         process := helperProcess("test-interface")
300         c := NewClient(&ClientConfig{
301                 Cmd:             process,
302                 HandshakeConfig: testHandshake,
303                 Plugins:         testPluginMap,
304         })
305         defer c.Kill()
306
307         // Grab the RPC client
308         _, err := c.Client()
309         if err != nil {
310                 t.Fatalf("err should be nil, got %s", err)
311         }
312
313         // Get the reattach configuration
314         reattach := c.ReattachConfig()
315
316         // Create a new client
317         c = NewClient(&ClientConfig{
318                 Reattach:        reattach,
319                 HandshakeConfig: testHandshake,
320                 Plugins:         testPluginMap,
321         })
322
323         // Grab the RPC client
324         client, err := c.Client()
325         if err != nil {
326                 t.Fatalf("err should be nil, got %s", err)
327         }
328
329         // Grab the impl
330         raw, err := client.Dispense("test")
331         if err != nil {
332                 t.Fatalf("err should be nil, got %s", err)
333         }
334
335         impl, ok := raw.(testInterface)
336         if !ok {
337                 t.Fatalf("bad: %#v", raw)
338         }
339
340         result := impl.Double(21)
341         if result != 42 {
342                 t.Fatalf("bad: %#v", result)
343         }
344
345         // Kill it
346         c.Kill()
347
348         // Test that it knows it is exited
349         if !c.Exited() {
350                 t.Fatal("should say client has exited")
351         }
352 }
353
354 func TestClient_reattachNoProtocol(t *testing.T) {
355         process := helperProcess("test-interface")
356         c := NewClient(&ClientConfig{
357                 Cmd:             process,
358                 HandshakeConfig: testHandshake,
359                 Plugins:         testPluginMap,
360         })
361         defer c.Kill()
362
363         // Grab the RPC client
364         _, err := c.Client()
365         if err != nil {
366                 t.Fatalf("err should be nil, got %s", err)
367         }
368
369         // Get the reattach configuration
370         reattach := c.ReattachConfig()
371         reattach.Protocol = ""
372
373         // Create a new client
374         c = NewClient(&ClientConfig{
375                 Reattach:        reattach,
376                 HandshakeConfig: testHandshake,
377                 Plugins:         testPluginMap,
378         })
379
380         // Grab the RPC client
381         client, err := c.Client()
382         if err != nil {
383                 t.Fatalf("err should be nil, got %s", err)
384         }
385
386         // Grab the impl
387         raw, err := client.Dispense("test")
388         if err != nil {
389                 t.Fatalf("err should be nil, got %s", err)
390         }
391
392         impl, ok := raw.(testInterface)
393         if !ok {
394                 t.Fatalf("bad: %#v", raw)
395         }
396
397         result := impl.Double(21)
398         if result != 42 {
399                 t.Fatalf("bad: %#v", result)
400         }
401
402         // Kill it
403         c.Kill()
404
405         // Test that it knows it is exited
406         if !c.Exited() {
407                 t.Fatal("should say client has exited")
408         }
409 }
410
411 func TestClient_reattachGRPC(t *testing.T) {
412         process := helperProcess("test-grpc")
413         c := NewClient(&ClientConfig{
414                 Cmd:              process,
415                 HandshakeConfig:  testHandshake,
416                 Plugins:          testPluginMap,
417                 AllowedProtocols: []Protocol{ProtocolGRPC},
418         })
419         defer c.Kill()
420
421         // Grab the RPC client
422         _, err := c.Client()
423         if err != nil {
424                 t.Fatalf("err should be nil, got %s", err)
425         }
426
427         // Get the reattach configuration
428         reattach := c.ReattachConfig()
429
430         // Create a new client
431         c = NewClient(&ClientConfig{
432                 Reattach:         reattach,
433                 HandshakeConfig:  testHandshake,
434                 Plugins:          testPluginMap,
435                 AllowedProtocols: []Protocol{ProtocolGRPC},
436         })
437
438         // Grab the RPC client
439         client, err := c.Client()
440         if err != nil {
441                 t.Fatalf("err should be nil, got %s", err)
442         }
443
444         // Grab the impl
445         raw, err := client.Dispense("test")
446         if err != nil {
447                 t.Fatalf("err should be nil, got %s", err)
448         }
449
450         impl, ok := raw.(testInterface)
451         if !ok {
452                 t.Fatalf("bad: %#v", raw)
453         }
454
455         result := impl.Double(21)
456         if result != 42 {
457                 t.Fatalf("bad: %#v", result)
458         }
459
460         // Kill it
461         c.Kill()
462
463         // Test that it knows it is exited
464         if !c.Exited() {
465                 t.Fatal("should say client has exited")
466         }
467 }
468
469 func TestClient_reattachNotFound(t *testing.T) {
470         // Find a bad pid
471         var pid int = 5000
472         for i := pid; i < 32000; i++ {
473                 if _, err := os.FindProcess(i); err != nil {
474                         pid = i
475                         break
476                 }
477         }
478
479         // Addr that won't work
480         l, err := net.Listen("tcp", "127.0.0.1:0")
481         if err != nil {
482                 t.Fatalf("err: %s", err)
483         }
484         addr := l.Addr()
485         l.Close()
486
487         // Reattach
488         c := NewClient(&ClientConfig{
489                 Reattach: &ReattachConfig{
490                         Addr: addr,
491                         Pid:  pid,
492                 },
493                 HandshakeConfig: testHandshake,
494                 Plugins:         testPluginMap,
495         })
496
497         // Start shouldn't error
498         if _, err := c.Start(); err == nil {
499                 t.Fatal("should error")
500         } else if err != ErrProcessNotFound {
501                 t.Fatalf("err: %s", err)
502         }
503 }
504
505 func TestClientStart_badVersion(t *testing.T) {
506         config := &ClientConfig{
507                 Cmd:             helperProcess("bad-version"),
508                 StartTimeout:    50 * time.Millisecond,
509                 HandshakeConfig: testHandshake,
510         }
511
512         c := NewClient(config)
513         defer c.Kill()
514
515         _, err := c.Start()
516         if err == nil {
517                 t.Fatal("err should not be nil")
518         }
519 }
520
521 func TestClient_Start_Timeout(t *testing.T) {
522         config := &ClientConfig{
523                 Cmd:             helperProcess("start-timeout"),
524                 StartTimeout:    50 * time.Millisecond,
525                 HandshakeConfig: testHandshake,
526         }
527
528         c := NewClient(config)
529         defer c.Kill()
530
531         _, err := c.Start()
532         if err == nil {
533                 t.Fatal("err should not be nil")
534         }
535 }
536
537 func TestClient_Stderr(t *testing.T) {
538         stderr := new(bytes.Buffer)
539         process := helperProcess("stderr")
540         c := NewClient(&ClientConfig{
541                 Cmd:             process,
542                 Stderr:          stderr,
543                 HandshakeConfig: testHandshake,
544         })
545         defer c.Kill()
546
547         if _, err := c.Start(); err != nil {
548                 t.Fatalf("err: %s", err)
549         }
550
551         for !c.Exited() {
552                 time.Sleep(10 * time.Millisecond)
553         }
554
555         if !strings.Contains(stderr.String(), "HELLO\n") {
556                 t.Fatalf("bad log data: '%s'", stderr.String())
557         }
558
559         if !strings.Contains(stderr.String(), "WORLD\n") {
560                 t.Fatalf("bad log data: '%s'", stderr.String())
561         }
562 }
563
564 func TestClient_StderrJSON(t *testing.T) {
565         stderr := new(bytes.Buffer)
566         process := helperProcess("stderr-json")
567         c := NewClient(&ClientConfig{
568                 Cmd:             process,
569                 Stderr:          stderr,
570                 HandshakeConfig: testHandshake,
571         })
572         defer c.Kill()
573
574         if _, err := c.Start(); err != nil {
575                 t.Fatalf("err: %s", err)
576         }
577
578         for !c.Exited() {
579                 time.Sleep(10 * time.Millisecond)
580         }
581
582         if !strings.Contains(stderr.String(), "[\"HELLO\"]\n") {
583                 t.Fatalf("bad log data: '%s'", stderr.String())
584         }
585
586         if !strings.Contains(stderr.String(), "12345\n") {
587                 t.Fatalf("bad log data: '%s'", stderr.String())
588         }
589 }
590
591 func TestClient_Stdin(t *testing.T) {
592         // Overwrite stdin for this test with a temporary file
593         tf, err := ioutil.TempFile("", "terraform")
594         if err != nil {
595                 t.Fatalf("err: %s", err)
596         }
597         defer os.Remove(tf.Name())
598         defer tf.Close()
599
600         if _, err = tf.WriteString("hello"); err != nil {
601                 t.Fatalf("error: %s", err)
602         }
603
604         if err = tf.Sync(); err != nil {
605                 t.Fatalf("error: %s", err)
606         }
607
608         if _, err = tf.Seek(0, 0); err != nil {
609                 t.Fatalf("error: %s", err)
610         }
611
612         oldStdin := os.Stdin
613         defer func() { os.Stdin = oldStdin }()
614         os.Stdin = tf
615
616         process := helperProcess("stdin")
617         c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake})
618         defer c.Kill()
619
620         _, err = c.Start()
621         if err != nil {
622                 t.Fatalf("error: %s", err)
623         }
624
625         for {
626                 if c.Exited() {
627                         break
628                 }
629
630                 time.Sleep(50 * time.Millisecond)
631         }
632
633         if !process.ProcessState.Success() {
634                 t.Fatal("process didn't exit cleanly")
635         }
636 }
637
638 func TestClient_SecureConfig(t *testing.T) {
639         // Test failure case
640         secureConfig := &SecureConfig{
641                 Checksum: []byte{'1'},
642                 Hash:     sha256.New(),
643         }
644         process := helperProcess("test-interface")
645         c := NewClient(&ClientConfig{
646                 Cmd:             process,
647                 HandshakeConfig: testHandshake,
648                 Plugins:         testPluginMap,
649                 SecureConfig:    secureConfig,
650         })
651
652         // Grab the RPC client, should error
653         _, err := c.Client()
654         c.Kill()
655         if err != ErrChecksumsDoNotMatch {
656                 t.Fatalf("err should be %s, got %s", ErrChecksumsDoNotMatch, err)
657         }
658
659         // Get the checksum of the executable
660         file, err := os.Open(os.Args[0])
661         if err != nil {
662                 t.Fatal(err)
663         }
664         defer file.Close()
665
666         hash := sha256.New()
667
668         _, err = io.Copy(hash, file)
669         if err != nil {
670                 t.Fatal(err)
671         }
672
673         sum := hash.Sum(nil)
674
675         secureConfig = &SecureConfig{
676                 Checksum: sum,
677                 Hash:     sha256.New(),
678         }
679
680         c = NewClient(&ClientConfig{
681                 Cmd:             process,
682                 HandshakeConfig: testHandshake,
683                 Plugins:         testPluginMap,
684                 SecureConfig:    secureConfig,
685         })
686         defer c.Kill()
687
688         // Grab the RPC client
689         _, err = c.Client()
690         if err != nil {
691                 t.Fatalf("err should be nil, got %s", err)
692         }
693 }
694
695 func TestClient_TLS(t *testing.T) {
696         // Test failure case
697         process := helperProcess("test-interface-tls")
698         cBad := NewClient(&ClientConfig{
699                 Cmd:             process,
700                 HandshakeConfig: testHandshake,
701                 Plugins:         testPluginMap,
702         })
703         defer cBad.Kill()
704
705         // Grab the RPC client
706         clientBad, err := cBad.Client()
707         if err != nil {
708                 t.Fatalf("err should be nil, got %s", err)
709         }
710
711         // Grab the impl
712         raw, err := clientBad.Dispense("test")
713         if err == nil {
714                 t.Fatal("expected error, got nil")
715         }
716
717         cBad.Kill()
718
719         // Add TLS config to client
720         tlsConfig, err := helperTLSProvider()
721         if err != nil {
722                 t.Fatalf("err should be nil, got %s", err)
723         }
724
725         process = helperProcess("test-interface-tls")
726         c := NewClient(&ClientConfig{
727                 Cmd:             process,
728                 HandshakeConfig: testHandshake,
729                 Plugins:         testPluginMap,
730                 TLSConfig:       tlsConfig,
731         })
732         defer c.Kill()
733
734         // Grab the RPC client
735         client, err := c.Client()
736         if err != nil {
737                 t.Fatalf("err should be nil, got %s", err)
738         }
739
740         // Grab the impl
741         raw, err = client.Dispense("test")
742         if err != nil {
743                 t.Fatalf("err should be nil, got %s", err)
744         }
745
746         impl, ok := raw.(testInterface)
747         if !ok {
748                 t.Fatalf("bad: %#v", raw)
749         }
750
751         result := impl.Double(21)
752         if result != 42 {
753                 t.Fatalf("bad: %#v", result)
754         }
755
756         // Kill it
757         c.Kill()
758
759         // Test that it knows it is exited
760         if !c.Exited() {
761                 t.Fatal("should say client has exited")
762         }
763 }
764
765 func TestClient_TLS_grpc(t *testing.T) {
766         // Add TLS config to client
767         tlsConfig, err := helperTLSProvider()
768         if err != nil {
769                 t.Fatalf("err should be nil, got %s", err)
770         }
771
772         process := helperProcess("test-grpc-tls")
773         c := NewClient(&ClientConfig{
774                 Cmd:              process,
775                 HandshakeConfig:  testHandshake,
776                 Plugins:          testPluginMap,
777                 TLSConfig:        tlsConfig,
778                 AllowedProtocols: []Protocol{ProtocolGRPC},
779         })
780         defer c.Kill()
781
782         // Grab the RPC client
783         client, err := c.Client()
784         if err != nil {
785                 t.Fatalf("err should be nil, got %s", err)
786         }
787
788         // Grab the impl
789         raw, err := client.Dispense("test")
790         if err != nil {
791                 t.Fatalf("err should be nil, got %s", err)
792         }
793
794         impl, ok := raw.(testInterface)
795         if !ok {
796                 t.Fatalf("bad: %#v", raw)
797         }
798
799         result := impl.Double(21)
800         if result != 42 {
801                 t.Fatalf("bad: %#v", result)
802         }
803
804         // Kill it
805         c.Kill()
806
807         // Test that it knows it is exited
808         if !c.Exited() {
809                 t.Fatal("should say client has exited")
810         }
811 }
812
813 func TestClient_secureConfigAndReattach(t *testing.T) {
814         config := &ClientConfig{
815                 SecureConfig: &SecureConfig{},
816                 Reattach:     &ReattachConfig{},
817         }
818
819         c := NewClient(config)
820         defer c.Kill()
821
822         _, err := c.Start()
823         if err != ErrSecureConfigAndReattach {
824                 t.Fatalf("err should not be %s, got %s", ErrSecureConfigAndReattach, err)
825         }
826 }
827
828 func TestClient_ping(t *testing.T) {
829         process := helperProcess("test-interface")
830         c := NewClient(&ClientConfig{
831                 Cmd:             process,
832                 HandshakeConfig: testHandshake,
833                 Plugins:         testPluginMap,
834         })
835         defer c.Kill()
836
837         // Get the client
838         client, err := c.Client()
839         if err != nil {
840                 t.Fatalf("err: %s", err)
841         }
842
843         // Ping, should work
844         if err := client.Ping(); err != nil {
845                 t.Fatalf("err: %s", err)
846         }
847
848         // Kill it
849         c.Kill()
850         if err := client.Ping(); err == nil {
851                 t.Fatal("should error")
852         }
853 }
854
855 func TestClient_logger(t *testing.T) {
856         t.Run("net/rpc", func(t *testing.T) { testClient_logger(t, "netrpc") })
857         t.Run("grpc", func(t *testing.T) { testClient_logger(t, "grpc") })
858 }
859
860 func testClient_logger(t *testing.T, proto string) {
861         var buffer bytes.Buffer
862         mutex := new(sync.Mutex)
863         stderr := io.MultiWriter(os.Stderr, &buffer)
864         // Custom hclog.Logger
865         clientLogger := hclog.New(&hclog.LoggerOptions{
866                 Name:   "test-logger",
867                 Level:  hclog.Trace,
868                 Output: stderr,
869                 Mutex:  mutex,
870         })
871
872         process := helperProcess("test-interface-logger-" + proto)
873         c := NewClient(&ClientConfig{
874                 Cmd:              process,
875                 HandshakeConfig:  testHandshake,
876                 Plugins:          testPluginMap,
877                 Logger:           clientLogger,
878                 AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC},
879         })
880         defer c.Kill()
881
882         // Grab the RPC client
883         client, err := c.Client()
884         if err != nil {
885                 t.Fatalf("err should be nil, got %s", err)
886         }
887
888         // Grab the impl
889         raw, err := client.Dispense("test")
890         if err != nil {
891                 t.Fatalf("err should be nil, got %s", err)
892         }
893
894         impl, ok := raw.(testInterface)
895         if !ok {
896                 t.Fatalf("bad: %#v", raw)
897         }
898
899         {
900                 // Discard everything else, and capture the output we care about
901                 mutex.Lock()
902                 buffer.Reset()
903                 mutex.Unlock()
904                 impl.PrintKV("foo", "bar")
905                 time.Sleep(100 * time.Millisecond)
906                 mutex.Lock()
907                 line, err := buffer.ReadString('\n')
908                 mutex.Unlock()
909                 if err != nil {
910                         t.Fatal(err)
911                 }
912                 if !strings.Contains(line, "foo=bar") {
913                         t.Fatalf("bad: %q", line)
914                 }
915         }
916
917         {
918                 // Try an integer type
919                 mutex.Lock()
920                 buffer.Reset()
921                 mutex.Unlock()
922                 impl.PrintKV("foo", 12)
923                 time.Sleep(100 * time.Millisecond)
924                 mutex.Lock()
925                 line, err := buffer.ReadString('\n')
926                 mutex.Unlock()
927                 if err != nil {
928                         t.Fatal(err)
929                 }
930                 if !strings.Contains(line, "foo=12") {
931                         t.Fatalf("bad: %q", line)
932                 }
933         }
934
935         // Kill it
936         c.Kill()
937
938         // Test that it knows it is exited
939         if !c.Exited() {
940                 t.Fatal("should say client has exited")
941         }
942 }