github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/container/exec_test.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"io/ioutil"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/docker/cli/cli"
    10  	"github.com/docker/cli/cli/config/configfile"
    11  	"github.com/docker/cli/internal/test"
    12  	"github.com/docker/cli/opts"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/pkg/errors"
    15  	"gotest.tools/v3/assert"
    16  	is "gotest.tools/v3/assert/cmp"
    17  	"gotest.tools/v3/fs"
    18  )
    19  
    20  func withDefaultOpts(options execOptions) execOptions {
    21  	options.env = opts.NewListOpts(opts.ValidateEnv)
    22  	options.envFile = opts.NewListOpts(nil)
    23  	if len(options.command) == 0 {
    24  		options.command = []string{"command"}
    25  	}
    26  	return options
    27  }
    28  
    29  func TestParseExec(t *testing.T) {
    30  	content := `ONE=1
    31  TWO=2
    32  	`
    33  
    34  	tmpFile := fs.NewFile(t, t.Name(), fs.WithContent(content))
    35  	defer tmpFile.Remove()
    36  
    37  	testcases := []struct {
    38  		options    execOptions
    39  		configFile configfile.ConfigFile
    40  		expected   types.ExecConfig
    41  	}{
    42  		{
    43  			expected: types.ExecConfig{
    44  				Cmd:          []string{"command"},
    45  				AttachStdout: true,
    46  				AttachStderr: true,
    47  			},
    48  			options: withDefaultOpts(execOptions{}),
    49  		},
    50  		{
    51  			expected: types.ExecConfig{
    52  				Cmd:          []string{"command1", "command2"},
    53  				AttachStdout: true,
    54  				AttachStderr: true,
    55  			},
    56  			options: withDefaultOpts(execOptions{
    57  				command: []string{"command1", "command2"},
    58  			}),
    59  		},
    60  		{
    61  			options: withDefaultOpts(execOptions{
    62  				interactive: true,
    63  				tty:         true,
    64  				user:        "uid",
    65  			}),
    66  			expected: types.ExecConfig{
    67  				User:         "uid",
    68  				AttachStdin:  true,
    69  				AttachStdout: true,
    70  				AttachStderr: true,
    71  				Tty:          true,
    72  				Cmd:          []string{"command"},
    73  			},
    74  		},
    75  		{
    76  			options: withDefaultOpts(execOptions{detach: true}),
    77  			expected: types.ExecConfig{
    78  				Detach: true,
    79  				Cmd:    []string{"command"},
    80  			},
    81  		},
    82  		{
    83  			options: withDefaultOpts(execOptions{
    84  				tty:         true,
    85  				interactive: true,
    86  				detach:      true,
    87  			}),
    88  			expected: types.ExecConfig{
    89  				Detach: true,
    90  				Tty:    true,
    91  				Cmd:    []string{"command"},
    92  			},
    93  		},
    94  		{
    95  			options:    withDefaultOpts(execOptions{detach: true}),
    96  			configFile: configfile.ConfigFile{DetachKeys: "de"},
    97  			expected: types.ExecConfig{
    98  				Cmd:        []string{"command"},
    99  				DetachKeys: "de",
   100  				Detach:     true,
   101  			},
   102  		},
   103  		{
   104  			options: withDefaultOpts(execOptions{
   105  				detach:     true,
   106  				detachKeys: "ab",
   107  			}),
   108  			configFile: configfile.ConfigFile{DetachKeys: "de"},
   109  			expected: types.ExecConfig{
   110  				Cmd:        []string{"command"},
   111  				DetachKeys: "ab",
   112  				Detach:     true,
   113  			},
   114  		},
   115  		{
   116  			expected: types.ExecConfig{
   117  				Cmd:          []string{"command"},
   118  				AttachStdout: true,
   119  				AttachStderr: true,
   120  				Env:          []string{"ONE=1", "TWO=2"},
   121  			},
   122  			options: func() execOptions {
   123  				o := withDefaultOpts(execOptions{})
   124  				o.envFile.Set(tmpFile.Path())
   125  				return o
   126  			}(),
   127  		},
   128  		{
   129  			expected: types.ExecConfig{
   130  				Cmd:          []string{"command"},
   131  				AttachStdout: true,
   132  				AttachStderr: true,
   133  				Env:          []string{"ONE=1", "TWO=2", "ONE=override"},
   134  			},
   135  			options: func() execOptions {
   136  				o := withDefaultOpts(execOptions{})
   137  				o.envFile.Set(tmpFile.Path())
   138  				o.env.Set("ONE=override")
   139  				return o
   140  			}(),
   141  		},
   142  	}
   143  
   144  	for _, testcase := range testcases {
   145  		execConfig, err := parseExec(testcase.options, &testcase.configFile)
   146  		assert.NilError(t, err)
   147  		assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
   148  	}
   149  }
   150  
   151  func TestParseExecNoSuchFile(t *testing.T) {
   152  	execOpts := withDefaultOpts(execOptions{})
   153  	execOpts.envFile.Set("no-such-env-file")
   154  	execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
   155  	assert.ErrorContains(t, err, "no-such-env-file")
   156  	assert.Check(t, os.IsNotExist(err))
   157  	assert.Check(t, execConfig == nil)
   158  }
   159  
   160  func TestRunExec(t *testing.T) {
   161  	var testcases = []struct {
   162  		doc           string
   163  		options       execOptions
   164  		client        fakeClient
   165  		expectedError string
   166  		expectedOut   string
   167  		expectedErr   string
   168  	}{
   169  		{
   170  			doc: "successful detach",
   171  			options: withDefaultOpts(execOptions{
   172  				container: "thecontainer",
   173  				detach:    true,
   174  			}),
   175  			client: fakeClient{execCreateFunc: execCreateWithID},
   176  		},
   177  		{
   178  			doc:     "inspect error",
   179  			options: newExecOptions(),
   180  			client: fakeClient{
   181  				inspectFunc: func(string) (types.ContainerJSON, error) {
   182  					return types.ContainerJSON{}, errors.New("failed inspect")
   183  				},
   184  			},
   185  			expectedError: "failed inspect",
   186  		},
   187  		{
   188  			doc:           "missing exec ID",
   189  			options:       newExecOptions(),
   190  			expectedError: "exec ID empty",
   191  		},
   192  	}
   193  
   194  	for _, testcase := range testcases {
   195  		t.Run(testcase.doc, func(t *testing.T) {
   196  			cli := test.NewFakeCli(&testcase.client)
   197  
   198  			err := runExec(cli, testcase.options)
   199  			if testcase.expectedError != "" {
   200  				assert.ErrorContains(t, err, testcase.expectedError)
   201  			} else {
   202  				if !assert.Check(t, err) {
   203  					return
   204  				}
   205  			}
   206  			assert.Check(t, is.Equal(testcase.expectedOut, cli.OutBuffer().String()))
   207  			assert.Check(t, is.Equal(testcase.expectedErr, cli.ErrBuffer().String()))
   208  		})
   209  	}
   210  }
   211  
   212  func execCreateWithID(_ string, _ types.ExecConfig) (types.IDResponse, error) {
   213  	return types.IDResponse{ID: "execid"}, nil
   214  }
   215  
   216  func TestGetExecExitStatus(t *testing.T) {
   217  	execID := "the exec id"
   218  	expecatedErr := errors.New("unexpected error")
   219  
   220  	testcases := []struct {
   221  		inspectError  error
   222  		exitCode      int
   223  		expectedError error
   224  	}{
   225  		{
   226  			inspectError: nil,
   227  			exitCode:     0,
   228  		},
   229  		{
   230  			inspectError:  expecatedErr,
   231  			expectedError: expecatedErr,
   232  		},
   233  		{
   234  			exitCode:      15,
   235  			expectedError: cli.StatusError{StatusCode: 15},
   236  		},
   237  	}
   238  
   239  	for _, testcase := range testcases {
   240  		client := &fakeClient{
   241  			execInspectFunc: func(id string) (types.ContainerExecInspect, error) {
   242  				assert.Check(t, is.Equal(execID, id))
   243  				return types.ContainerExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
   244  			},
   245  		}
   246  		err := getExecExitStatus(context.Background(), client, execID)
   247  		assert.Check(t, is.Equal(testcase.expectedError, err))
   248  	}
   249  }
   250  
   251  func TestNewExecCommandErrors(t *testing.T) {
   252  	testCases := []struct {
   253  		name                 string
   254  		args                 []string
   255  		expectedError        string
   256  		containerInspectFunc func(img string) (types.ContainerJSON, error)
   257  	}{
   258  		{
   259  			name:          "client-error",
   260  			args:          []string{"5cb5bb5e4a3b", "-t", "-i", "bash"},
   261  			expectedError: "something went wrong",
   262  			containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
   263  				return types.ContainerJSON{}, errors.Errorf("something went wrong")
   264  			},
   265  		},
   266  	}
   267  	for _, tc := range testCases {
   268  		cli := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
   269  		cmd := NewExecCommand(cli)
   270  		cmd.SetOut(ioutil.Discard)
   271  		cmd.SetArgs(tc.args)
   272  		assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
   273  	}
   274  }