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 }