github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/exec_test.go (about) 1 package container 2 3 import ( 4 "context" 5 "io" 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 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 Detach: true, 173 }), 174 client: fakeClient{execCreateFunc: execCreateWithID}, 175 }, 176 { 177 doc: "inspect error", 178 options: NewExecOptions(), 179 client: fakeClient{ 180 inspectFunc: func(string) (types.ContainerJSON, error) { 181 return types.ContainerJSON{}, errors.New("failed inspect") 182 }, 183 }, 184 expectedError: "failed inspect", 185 }, 186 { 187 doc: "missing exec ID", 188 options: NewExecOptions(), 189 expectedError: "exec ID empty", 190 }, 191 } 192 193 for _, testcase := range testcases { 194 t.Run(testcase.doc, func(t *testing.T) { 195 fakeCLI := test.NewFakeCli(&testcase.client) 196 197 err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options) 198 if testcase.expectedError != "" { 199 assert.ErrorContains(t, err, testcase.expectedError) 200 } else if !assert.Check(t, err) { 201 return 202 } 203 assert.Check(t, is.Equal(testcase.expectedOut, fakeCLI.OutBuffer().String())) 204 assert.Check(t, is.Equal(testcase.expectedErr, fakeCLI.ErrBuffer().String())) 205 }) 206 } 207 } 208 209 func execCreateWithID(_ string, _ types.ExecConfig) (types.IDResponse, error) { 210 return types.IDResponse{ID: "execid"}, nil 211 } 212 213 func TestGetExecExitStatus(t *testing.T) { 214 execID := "the exec id" 215 expectedErr := errors.New("unexpected error") 216 217 testcases := []struct { 218 inspectError error 219 exitCode int 220 expectedError error 221 }{ 222 { 223 inspectError: nil, 224 exitCode: 0, 225 }, 226 { 227 inspectError: expectedErr, 228 expectedError: expectedErr, 229 }, 230 { 231 exitCode: 15, 232 expectedError: cli.StatusError{StatusCode: 15}, 233 }, 234 } 235 236 for _, testcase := range testcases { 237 client := &fakeClient{ 238 execInspectFunc: func(id string) (types.ContainerExecInspect, error) { 239 assert.Check(t, is.Equal(execID, id)) 240 return types.ContainerExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError 241 }, 242 } 243 err := getExecExitStatus(context.Background(), client, execID) 244 assert.Check(t, is.Equal(testcase.expectedError, err)) 245 } 246 } 247 248 func TestNewExecCommandErrors(t *testing.T) { 249 testCases := []struct { 250 name string 251 args []string 252 expectedError string 253 containerInspectFunc func(img string) (types.ContainerJSON, error) 254 }{ 255 { 256 name: "client-error", 257 args: []string{"5cb5bb5e4a3b", "-t", "-i", "bash"}, 258 expectedError: "something went wrong", 259 containerInspectFunc: func(containerID string) (types.ContainerJSON, error) { 260 return types.ContainerJSON{}, errors.Errorf("something went wrong") 261 }, 262 }, 263 } 264 for _, tc := range testCases { 265 fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}) 266 cmd := NewExecCommand(fakeCLI) 267 cmd.SetOut(io.Discard) 268 cmd.SetArgs(tc.args) 269 assert.ErrorContains(t, cmd.Execute(), tc.expectedError) 270 } 271 }