+++ /dev/null
-package plugin
-
-import (
- "bytes"
- "crypto/sha256"
- "io"
- "io/ioutil"
- "net"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "testing"
- "time"
-
- hclog "github.com/hashicorp/go-hclog"
-)
-
-func TestClient(t *testing.T) {
- process := helperProcess("mock")
- c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake})
- defer c.Kill()
-
- // Test that it parses the proper address
- addr, err := c.Start()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- if addr.Network() != "tcp" {
- t.Fatalf("bad: %#v", addr)
- }
-
- if addr.String() != ":1234" {
- t.Fatalf("bad: %#v", addr)
- }
-
- // Test that it exits properly if killed
- c.Kill()
-
- if process.ProcessState == nil {
- t.Fatal("should have process state")
- }
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-// This tests a bug where Kill would start
-func TestClient_killStart(t *testing.T) {
- // Create a temporary dir to store the result file
- td, err := ioutil.TempDir("", "plugin")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- defer os.RemoveAll(td)
-
- // Start the client
- path := filepath.Join(td, "booted")
- process := helperProcess("bad-version", path)
- c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake})
- defer c.Kill()
-
- // Verify our path doesn't exist
- if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
- t.Fatalf("bad: %s", err)
- }
-
- // Test that it parses the proper address
- if _, err := c.Start(); err == nil {
- t.Fatal("expected error")
- }
-
- // Verify we started
- if _, err := os.Stat(path); err != nil {
- t.Fatalf("bad: %s", err)
- }
- if err := os.Remove(path); err != nil {
- t.Fatalf("bad: %s", err)
- }
-
- // Test that Kill does nothing really
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-
- if process.ProcessState == nil {
- t.Fatal("should have no process state")
- }
-
- // Verify our path doesn't exist
- if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
- t.Fatalf("bad: %s", err)
- }
-}
-
-func TestClient_testCleanup(t *testing.T) {
- // Create a temporary dir to store the result file
- td, err := ioutil.TempDir("", "plugin")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- defer os.RemoveAll(td)
-
- // Create a path that the helper process will write on cleanup
- path := filepath.Join(td, "output")
-
- // Test the cleanup
- process := helperProcess("cleanup", path)
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
-
- // Grab the client so the process starts
- if _, err := c.Client(); err != nil {
- c.Kill()
- t.Fatalf("err: %s", err)
- }
-
- // Kill it gracefully
- c.Kill()
-
- // Test for the file
- if _, err := os.Stat(path); err != nil {
- t.Fatalf("err: %s", err)
- }
-}
-
-func TestClient_testInterface(t *testing.T) {
- process := helperProcess("test-interface")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
- defer c.Kill()
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- result := impl.Double(21)
- if result != 42 {
- t.Fatalf("bad: %#v", result)
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-func TestClient_grpc_servercrash(t *testing.T) {
- process := helperProcess("test-grpc")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- AllowedProtocols: []Protocol{ProtocolGRPC},
- })
- defer c.Kill()
-
- if _, err := c.Start(); err != nil {
- t.Fatalf("err: %s", err)
- }
-
- if v := c.Protocol(); v != ProtocolGRPC {
- t.Fatalf("bad: %s", v)
- }
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- _, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- c.process.Kill()
-
- select {
- case <-c.doneCtx.Done():
- case <-time.After(time.Second * 2):
- t.Fatal("Context was not closed")
- }
-}
-
-func TestClient_grpc(t *testing.T) {
- process := helperProcess("test-grpc")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- AllowedProtocols: []Protocol{ProtocolGRPC},
- })
- defer c.Kill()
-
- if _, err := c.Start(); err != nil {
- t.Fatalf("err: %s", err)
- }
-
- if v := c.Protocol(); v != ProtocolGRPC {
- t.Fatalf("bad: %s", v)
- }
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- result := impl.Double(21)
- if result != 42 {
- t.Fatalf("bad: %#v", result)
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-func TestClient_grpcNotAllowed(t *testing.T) {
- process := helperProcess("test-grpc")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
- defer c.Kill()
-
- if _, err := c.Start(); err == nil {
- t.Fatal("should error")
- }
-}
-
-func TestClient_cmdAndReattach(t *testing.T) {
- config := &ClientConfig{
- Cmd: helperProcess("start-timeout"),
- Reattach: &ReattachConfig{},
- }
-
- c := NewClient(config)
- defer c.Kill()
-
- _, err := c.Start()
- if err == nil {
- t.Fatal("err should not be nil")
- }
-}
-
-func TestClient_reattach(t *testing.T) {
- process := helperProcess("test-interface")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
- defer c.Kill()
-
- // Grab the RPC client
- _, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Get the reattach configuration
- reattach := c.ReattachConfig()
-
- // Create a new client
- c = NewClient(&ClientConfig{
- Reattach: reattach,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- result := impl.Double(21)
- if result != 42 {
- t.Fatalf("bad: %#v", result)
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-func TestClient_reattachNoProtocol(t *testing.T) {
- process := helperProcess("test-interface")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
- defer c.Kill()
-
- // Grab the RPC client
- _, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Get the reattach configuration
- reattach := c.ReattachConfig()
- reattach.Protocol = ""
-
- // Create a new client
- c = NewClient(&ClientConfig{
- Reattach: reattach,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- result := impl.Double(21)
- if result != 42 {
- t.Fatalf("bad: %#v", result)
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-func TestClient_reattachGRPC(t *testing.T) {
- process := helperProcess("test-grpc")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- AllowedProtocols: []Protocol{ProtocolGRPC},
- })
- defer c.Kill()
-
- // Grab the RPC client
- _, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Get the reattach configuration
- reattach := c.ReattachConfig()
-
- // Create a new client
- c = NewClient(&ClientConfig{
- Reattach: reattach,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- AllowedProtocols: []Protocol{ProtocolGRPC},
- })
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- result := impl.Double(21)
- if result != 42 {
- t.Fatalf("bad: %#v", result)
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-func TestClient_reattachNotFound(t *testing.T) {
- // Find a bad pid
- var pid int = 5000
- for i := pid; i < 32000; i++ {
- if _, err := os.FindProcess(i); err != nil {
- pid = i
- break
- }
- }
-
- // Addr that won't work
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- addr := l.Addr()
- l.Close()
-
- // Reattach
- c := NewClient(&ClientConfig{
- Reattach: &ReattachConfig{
- Addr: addr,
- Pid: pid,
- },
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
-
- // Start shouldn't error
- if _, err := c.Start(); err == nil {
- t.Fatal("should error")
- } else if err != ErrProcessNotFound {
- t.Fatalf("err: %s", err)
- }
-}
-
-func TestClientStart_badVersion(t *testing.T) {
- config := &ClientConfig{
- Cmd: helperProcess("bad-version"),
- StartTimeout: 50 * time.Millisecond,
- HandshakeConfig: testHandshake,
- }
-
- c := NewClient(config)
- defer c.Kill()
-
- _, err := c.Start()
- if err == nil {
- t.Fatal("err should not be nil")
- }
-}
-
-func TestClient_Start_Timeout(t *testing.T) {
- config := &ClientConfig{
- Cmd: helperProcess("start-timeout"),
- StartTimeout: 50 * time.Millisecond,
- HandshakeConfig: testHandshake,
- }
-
- c := NewClient(config)
- defer c.Kill()
-
- _, err := c.Start()
- if err == nil {
- t.Fatal("err should not be nil")
- }
-}
-
-func TestClient_Stderr(t *testing.T) {
- stderr := new(bytes.Buffer)
- process := helperProcess("stderr")
- c := NewClient(&ClientConfig{
- Cmd: process,
- Stderr: stderr,
- HandshakeConfig: testHandshake,
- })
- defer c.Kill()
-
- if _, err := c.Start(); err != nil {
- t.Fatalf("err: %s", err)
- }
-
- for !c.Exited() {
- time.Sleep(10 * time.Millisecond)
- }
-
- if !strings.Contains(stderr.String(), "HELLO\n") {
- t.Fatalf("bad log data: '%s'", stderr.String())
- }
-
- if !strings.Contains(stderr.String(), "WORLD\n") {
- t.Fatalf("bad log data: '%s'", stderr.String())
- }
-}
-
-func TestClient_StderrJSON(t *testing.T) {
- stderr := new(bytes.Buffer)
- process := helperProcess("stderr-json")
- c := NewClient(&ClientConfig{
- Cmd: process,
- Stderr: stderr,
- HandshakeConfig: testHandshake,
- })
- defer c.Kill()
-
- if _, err := c.Start(); err != nil {
- t.Fatalf("err: %s", err)
- }
-
- for !c.Exited() {
- time.Sleep(10 * time.Millisecond)
- }
-
- if !strings.Contains(stderr.String(), "[\"HELLO\"]\n") {
- t.Fatalf("bad log data: '%s'", stderr.String())
- }
-
- if !strings.Contains(stderr.String(), "12345\n") {
- t.Fatalf("bad log data: '%s'", stderr.String())
- }
-}
-
-func TestClient_Stdin(t *testing.T) {
- // Overwrite stdin for this test with a temporary file
- tf, err := ioutil.TempFile("", "terraform")
- if err != nil {
- t.Fatalf("err: %s", err)
- }
- defer os.Remove(tf.Name())
- defer tf.Close()
-
- if _, err = tf.WriteString("hello"); err != nil {
- t.Fatalf("error: %s", err)
- }
-
- if err = tf.Sync(); err != nil {
- t.Fatalf("error: %s", err)
- }
-
- if _, err = tf.Seek(0, 0); err != nil {
- t.Fatalf("error: %s", err)
- }
-
- oldStdin := os.Stdin
- defer func() { os.Stdin = oldStdin }()
- os.Stdin = tf
-
- process := helperProcess("stdin")
- c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake})
- defer c.Kill()
-
- _, err = c.Start()
- if err != nil {
- t.Fatalf("error: %s", err)
- }
-
- for {
- if c.Exited() {
- break
- }
-
- time.Sleep(50 * time.Millisecond)
- }
-
- if !process.ProcessState.Success() {
- t.Fatal("process didn't exit cleanly")
- }
-}
-
-func TestClient_SecureConfig(t *testing.T) {
- // Test failure case
- secureConfig := &SecureConfig{
- Checksum: []byte{'1'},
- Hash: sha256.New(),
- }
- process := helperProcess("test-interface")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- SecureConfig: secureConfig,
- })
-
- // Grab the RPC client, should error
- _, err := c.Client()
- c.Kill()
- if err != ErrChecksumsDoNotMatch {
- t.Fatalf("err should be %s, got %s", ErrChecksumsDoNotMatch, err)
- }
-
- // Get the checksum of the executable
- file, err := os.Open(os.Args[0])
- if err != nil {
- t.Fatal(err)
- }
- defer file.Close()
-
- hash := sha256.New()
-
- _, err = io.Copy(hash, file)
- if err != nil {
- t.Fatal(err)
- }
-
- sum := hash.Sum(nil)
-
- secureConfig = &SecureConfig{
- Checksum: sum,
- Hash: sha256.New(),
- }
-
- c = NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- SecureConfig: secureConfig,
- })
- defer c.Kill()
-
- // Grab the RPC client
- _, err = c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-}
-
-func TestClient_TLS(t *testing.T) {
- // Test failure case
- process := helperProcess("test-interface-tls")
- cBad := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
- defer cBad.Kill()
-
- // Grab the RPC client
- clientBad, err := cBad.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := clientBad.Dispense("test")
- if err == nil {
- t.Fatal("expected error, got nil")
- }
-
- cBad.Kill()
-
- // Add TLS config to client
- tlsConfig, err := helperTLSProvider()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- process = helperProcess("test-interface-tls")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- TLSConfig: tlsConfig,
- })
- defer c.Kill()
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err = client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- result := impl.Double(21)
- if result != 42 {
- t.Fatalf("bad: %#v", result)
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-func TestClient_TLS_grpc(t *testing.T) {
- // Add TLS config to client
- tlsConfig, err := helperTLSProvider()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- process := helperProcess("test-grpc-tls")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- TLSConfig: tlsConfig,
- AllowedProtocols: []Protocol{ProtocolGRPC},
- })
- defer c.Kill()
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- result := impl.Double(21)
- if result != 42 {
- t.Fatalf("bad: %#v", result)
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}
-
-func TestClient_secureConfigAndReattach(t *testing.T) {
- config := &ClientConfig{
- SecureConfig: &SecureConfig{},
- Reattach: &ReattachConfig{},
- }
-
- c := NewClient(config)
- defer c.Kill()
-
- _, err := c.Start()
- if err != ErrSecureConfigAndReattach {
- t.Fatalf("err should not be %s, got %s", ErrSecureConfigAndReattach, err)
- }
-}
-
-func TestClient_ping(t *testing.T) {
- process := helperProcess("test-interface")
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- })
- defer c.Kill()
-
- // Get the client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err: %s", err)
- }
-
- // Ping, should work
- if err := client.Ping(); err != nil {
- t.Fatalf("err: %s", err)
- }
-
- // Kill it
- c.Kill()
- if err := client.Ping(); err == nil {
- t.Fatal("should error")
- }
-}
-
-func TestClient_logger(t *testing.T) {
- t.Run("net/rpc", func(t *testing.T) { testClient_logger(t, "netrpc") })
- t.Run("grpc", func(t *testing.T) { testClient_logger(t, "grpc") })
-}
-
-func testClient_logger(t *testing.T, proto string) {
- var buffer bytes.Buffer
- mutex := new(sync.Mutex)
- stderr := io.MultiWriter(os.Stderr, &buffer)
- // Custom hclog.Logger
- clientLogger := hclog.New(&hclog.LoggerOptions{
- Name: "test-logger",
- Level: hclog.Trace,
- Output: stderr,
- Mutex: mutex,
- })
-
- process := helperProcess("test-interface-logger-" + proto)
- c := NewClient(&ClientConfig{
- Cmd: process,
- HandshakeConfig: testHandshake,
- Plugins: testPluginMap,
- Logger: clientLogger,
- AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC},
- })
- defer c.Kill()
-
- // Grab the RPC client
- client, err := c.Client()
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- // Grab the impl
- raw, err := client.Dispense("test")
- if err != nil {
- t.Fatalf("err should be nil, got %s", err)
- }
-
- impl, ok := raw.(testInterface)
- if !ok {
- t.Fatalf("bad: %#v", raw)
- }
-
- {
- // Discard everything else, and capture the output we care about
- mutex.Lock()
- buffer.Reset()
- mutex.Unlock()
- impl.PrintKV("foo", "bar")
- time.Sleep(100 * time.Millisecond)
- mutex.Lock()
- line, err := buffer.ReadString('\n')
- mutex.Unlock()
- if err != nil {
- t.Fatal(err)
- }
- if !strings.Contains(line, "foo=bar") {
- t.Fatalf("bad: %q", line)
- }
- }
-
- {
- // Try an integer type
- mutex.Lock()
- buffer.Reset()
- mutex.Unlock()
- impl.PrintKV("foo", 12)
- time.Sleep(100 * time.Millisecond)
- mutex.Lock()
- line, err := buffer.ReadString('\n')
- mutex.Unlock()
- if err != nil {
- t.Fatal(err)
- }
- if !strings.Contains(line, "foo=12") {
- t.Fatalf("bad: %q", line)
- }
- }
-
- // Kill it
- c.Kill()
-
- // Test that it knows it is exited
- if !c.Exited() {
- t.Fatal("should say client has exited")
- }
-}