github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_run_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bufio" 21 "bytes" 22 "errors" 23 "fmt" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "regexp" 28 "runtime" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/containerd/nerdctl/pkg/testutil" 34 "gotest.tools/v3/assert" 35 "gotest.tools/v3/icmd" 36 "gotest.tools/v3/poll" 37 ) 38 39 func TestRunEntrypointWithBuild(t *testing.T) { 40 t.Parallel() 41 testutil.RequiresBuild(t) 42 base := testutil.NewBase(t) 43 defer base.Cmd("builder", "prune").Run() 44 imageName := testutil.Identifier(t) 45 defer base.Cmd("rmi", imageName).Run() 46 47 dockerfile := fmt.Sprintf(`FROM %s 48 ENTRYPOINT ["echo", "foo"] 49 CMD ["echo", "bar"] 50 `, testutil.CommonImage) 51 52 buildCtx, err := createBuildContext(dockerfile) 53 assert.NilError(t, err) 54 defer os.RemoveAll(buildCtx) 55 56 base.Cmd("build", "-t", imageName, buildCtx).AssertOK() 57 base.Cmd("run", "--rm", imageName).AssertOutExactly("foo echo bar\n") 58 base.Cmd("run", "--rm", "--entrypoint", "", imageName).AssertFail() 59 base.Cmd("run", "--rm", "--entrypoint", "", imageName, "echo", "blah").AssertOutWithFunc(func(stdout string) error { 60 if !strings.Contains(stdout, "blah") { 61 return errors.New("echo blah was not executed?") 62 } 63 if strings.Contains(stdout, "bar") { 64 return errors.New("echo bar should not be executed") 65 } 66 if strings.Contains(stdout, "foo") { 67 return errors.New("echo foo should not be executed") 68 } 69 return nil 70 }) 71 base.Cmd("run", "--rm", "--entrypoint", "time", imageName).AssertFail() 72 base.Cmd("run", "--rm", "--entrypoint", "time", imageName, "echo", "blah").AssertOutWithFunc(func(stdout string) error { 73 if !strings.Contains(stdout, "blah") { 74 return errors.New("echo blah was not executed?") 75 } 76 if strings.Contains(stdout, "bar") { 77 return errors.New("echo bar should not be executed") 78 } 79 if strings.Contains(stdout, "foo") { 80 return errors.New("echo foo should not be executed") 81 } 82 return nil 83 }) 84 } 85 86 func TestRunWorkdir(t *testing.T) { 87 t.Parallel() 88 base := testutil.NewBase(t) 89 dir := "/foo" 90 if runtime.GOOS == "windows" { 91 dir = "c:" + dir 92 } 93 cmd := base.Cmd("run", "--rm", "--workdir="+dir, testutil.CommonImage, "pwd") 94 cmd.AssertOutContains("/foo") 95 } 96 97 func TestRunWithDoubleDash(t *testing.T) { 98 t.Parallel() 99 testutil.DockerIncompatible(t) 100 base := testutil.NewBase(t) 101 base.Cmd("run", "--rm", testutil.CommonImage, "--", "sh", "-euxc", "exit 0").AssertOK() 102 } 103 104 func TestRunExitCode(t *testing.T) { 105 t.Parallel() 106 base := testutil.NewBase(t) 107 tID := testutil.Identifier(t) 108 testContainer0 := tID + "-0" 109 testContainer123 := tID + "-123" 110 defer base.Cmd("rm", "-f", testContainer0, testContainer123).Run() 111 112 base.Cmd("run", "--name", testContainer0, testutil.CommonImage, "sh", "-euxc", "exit 0").AssertOK() 113 base.Cmd("run", "--name", testContainer123, testutil.CommonImage, "sh", "-euxc", "exit 123").AssertExitCode(123) 114 base.Cmd("ps", "-a").AssertOutWithFunc(func(stdout string) error { 115 if !strings.Contains(stdout, "Exited (0)") { 116 return fmt.Errorf("no entry for %q", testContainer0) 117 } 118 if !strings.Contains(stdout, "Exited (123)") { 119 return fmt.Errorf("no entry for %q", testContainer123) 120 } 121 return nil 122 }) 123 124 inspect0 := base.InspectContainer(testContainer0) 125 assert.Equal(base.T, "exited", inspect0.State.Status) 126 assert.Equal(base.T, 0, inspect0.State.ExitCode) 127 128 inspect123 := base.InspectContainer(testContainer123) 129 assert.Equal(base.T, "exited", inspect123.State.Status) 130 assert.Equal(base.T, 123, inspect123.State.ExitCode) 131 } 132 133 func TestRunCIDFile(t *testing.T) { 134 t.Parallel() 135 base := testutil.NewBase(t) 136 fileName := filepath.Join(t.TempDir(), "cid.file") 137 138 base.Cmd("run", "--rm", "--cidfile", fileName, testutil.CommonImage).AssertOK() 139 defer os.Remove(fileName) 140 141 _, err := os.Stat(fileName) 142 assert.NilError(base.T, err) 143 144 base.Cmd("run", "--rm", "--cidfile", fileName, testutil.CommonImage).AssertFail() 145 } 146 147 func TestRunEnvFile(t *testing.T) { 148 t.Parallel() 149 base := testutil.NewBase(t) 150 base.Env = append(os.Environ(), "HOST_ENV=ENV-IN-HOST") 151 152 tID := testutil.Identifier(t) 153 file1, err := os.CreateTemp("", tID) 154 assert.NilError(base.T, err) 155 path1 := file1.Name() 156 defer file1.Close() 157 defer os.Remove(path1) 158 err = os.WriteFile(path1, []byte("# this is a comment line\nTESTKEY1=TESTVAL1"), 0666) 159 assert.NilError(base.T, err) 160 161 file2, err := os.CreateTemp("", tID) 162 assert.NilError(base.T, err) 163 path2 := file2.Name() 164 defer file2.Close() 165 defer os.Remove(path2) 166 err = os.WriteFile(path2, []byte("# this is a comment line\nTESTKEY2=TESTVAL2\nHOST_ENV"), 0666) 167 assert.NilError(base.T, err) 168 169 base.Cmd("run", "--rm", "--env-file", path1, "--env-file", path2, testutil.CommonImage, "sh", "-c", "echo -n $TESTKEY1").AssertOutExactly("TESTVAL1") 170 base.Cmd("run", "--rm", "--env-file", path1, "--env-file", path2, testutil.CommonImage, "sh", "-c", "echo -n $TESTKEY2").AssertOutExactly("TESTVAL2") 171 base.Cmd("run", "--rm", "--env-file", path1, "--env-file", path2, testutil.CommonImage, "sh", "-c", "echo -n $HOST_ENV").AssertOutExactly("ENV-IN-HOST") 172 } 173 174 func TestRunEnv(t *testing.T) { 175 t.Parallel() 176 base := testutil.NewBase(t) 177 base.Env = append(os.Environ(), "CORGE=corge-value-in-host", "GARPLY=garply-value-in-host") 178 base.Cmd("run", "--rm", 179 "--env", "FOO=foo1,foo2", 180 "--env", "BAR=bar1 bar2", 181 "--env", "BAZ=", 182 "--env", "QUX", // not exported in OS 183 "--env", "QUUX=quux1", 184 "--env", "QUUX=quux2", 185 "--env", "CORGE", // OS exported 186 "--env", "GRAULT=grault_key=grault_value", // value contains `=` char 187 "--env", "GARPLY=", // OS exported 188 "--env", "WALDO=", // not exported in OS 189 190 testutil.CommonImage, "env").AssertOutWithFunc(func(stdout string) error { 191 if !strings.Contains(stdout, "\nFOO=foo1,foo2\n") { 192 return errors.New("got bad FOO") 193 } 194 if !strings.Contains(stdout, "\nBAR=bar1 bar2\n") { 195 return errors.New("got bad BAR") 196 } 197 if !strings.Contains(stdout, "\nBAZ=\n") && runtime.GOOS != "windows" { 198 return errors.New("got bad BAZ") 199 } 200 if strings.Contains(stdout, "QUX") { 201 return errors.New("got bad QUX (should not be set)") 202 } 203 if !strings.Contains(stdout, "\nQUUX=quux2\n") { 204 return errors.New("got bad QUUX") 205 } 206 if !strings.Contains(stdout, "\nCORGE=corge-value-in-host\n") { 207 return errors.New("got bad CORGE") 208 } 209 if !strings.Contains(stdout, "\nGRAULT=grault_key=grault_value\n") { 210 return errors.New("got bad GRAULT") 211 } 212 if !strings.Contains(stdout, "\nGARPLY=\n") && runtime.GOOS != "windows" { 213 return errors.New("got bad GARPLY") 214 } 215 if !strings.Contains(stdout, "\nWALDO=\n") && runtime.GOOS != "windows" { 216 return errors.New("got bad WALDO") 217 } 218 219 return nil 220 }) 221 } 222 223 func TestRunStdin(t *testing.T) { 224 t.Parallel() 225 base := testutil.NewBase(t) 226 if testutil.GetTarget() == testutil.Nerdctl { 227 testutil.RequireDaemonVersion(base, ">= 1.6.0-0") 228 } 229 230 const testStr = "test-run-stdin" 231 opts := []func(*testutil.Cmd){ 232 testutil.WithStdin(strings.NewReader(testStr)), 233 } 234 base.Cmd("run", "--rm", "-i", testutil.CommonImage, "cat").CmdOption(opts...).AssertOutExactly(testStr) 235 } 236 237 func TestRunWithJsonFileLogDriver(t *testing.T) { 238 if runtime.GOOS == "windows" { 239 t.Skip("json-file log driver is not yet implemented on Windows") 240 } 241 base := testutil.NewBase(t) 242 containerName := testutil.Identifier(t) 243 244 defer base.Cmd("rm", "-f", containerName).AssertOK() 245 base.Cmd("run", "-d", "--log-driver", "json-file", "--log-opt", "max-size=5K", "--log-opt", "max-file=2", "--name", containerName, testutil.CommonImage, 246 "sh", "-euxc", "hexdump -C /dev/urandom | head -n1000").AssertOK() 247 248 time.Sleep(3 * time.Second) 249 inspectedContainer := base.InspectContainer(containerName) 250 logJSONPath := filepath.Dir(inspectedContainer.LogPath) 251 // matches = current log file + old log files to retain 252 matches, err := filepath.Glob(filepath.Join(logJSONPath, inspectedContainer.ID+"*")) 253 assert.NilError(t, err) 254 if len(matches) != 2 { 255 t.Fatalf("the number of log files is not equal to 2 files, got: %s", matches) 256 } 257 for _, file := range matches { 258 fInfo, err := os.Stat(file) 259 assert.NilError(t, err) 260 // The log file size is compared to 5200 bytes (instead 5k) to keep docker compatibility. 261 // Docker log rotation lacks precision because the size check is done at the log entry level 262 // and not at the byte level (io.Writer), so docker log files can exceed 5k 263 if fInfo.Size() > 5200 { 264 t.Fatal("file size exceeded 5k") 265 } 266 } 267 } 268 269 func TestRunWithJsonFileLogDriverAndLogPathOpt(t *testing.T) { 270 if runtime.GOOS == "windows" { 271 t.Skip("json-file log driver is not yet implemented on Windows") 272 } 273 testutil.DockerIncompatible(t) 274 base := testutil.NewBase(t) 275 containerName := testutil.Identifier(t) 276 277 defer base.Cmd("rm", "-f", containerName).AssertOK() 278 customLogJSONPath := filepath.Join(t.TempDir(), containerName, containerName+"-json.log") 279 base.Cmd("run", "-d", "--log-driver", "json-file", "--log-opt", fmt.Sprintf("log-path=%s", customLogJSONPath), "--log-opt", "max-size=5K", "--log-opt", "max-file=2", "--name", containerName, testutil.CommonImage, 280 "sh", "-euxc", "hexdump -C /dev/urandom | head -n1000").AssertOK() 281 282 time.Sleep(3 * time.Second) 283 rawBytes, err := os.ReadFile(customLogJSONPath) 284 assert.NilError(t, err) 285 if len(rawBytes) == 0 { 286 t.Fatalf("logs are not written correctly to log-path: %s", customLogJSONPath) 287 } 288 289 // matches = current log file + old log files to retain 290 matches, err := filepath.Glob(filepath.Join(filepath.Dir(customLogJSONPath), containerName+"*")) 291 assert.NilError(t, err) 292 if len(matches) != 2 { 293 t.Fatalf("the number of log files is not equal to 2 files, got: %s", matches) 294 } 295 for _, file := range matches { 296 fInfo, err := os.Stat(file) 297 assert.NilError(t, err) 298 if fInfo.Size() > 5200 { 299 t.Fatal("file size exceeded 5k") 300 } 301 } 302 } 303 304 func TestRunWithJournaldLogDriver(t *testing.T) { 305 if runtime.GOOS == "windows" { 306 t.Skip("journald log driver is not yet implemented on Windows") 307 } 308 base := testutil.NewBase(t) 309 containerName := testutil.Identifier(t) 310 311 defer base.Cmd("rm", "-f", containerName).AssertOK() 312 base.Cmd("run", "-d", "--log-driver", "journald", "--name", containerName, testutil.CommonImage, 313 "sh", "-euxc", "echo foo; echo bar").AssertOK() 314 315 time.Sleep(3 * time.Second) 316 journalctl, err := exec.LookPath("journalctl") 317 assert.NilError(t, err) 318 inspectedContainer := base.InspectContainer(containerName) 319 found := 0 320 check := func(log poll.LogT) poll.Result { 321 res := icmd.RunCmd(icmd.Command(journalctl, "--no-pager", "--since", "2 minutes ago", fmt.Sprintf("SYSLOG_IDENTIFIER=%s", inspectedContainer.ID[:12]))) 322 assert.Equal(t, 0, res.ExitCode, res.Combined()) 323 if strings.Contains(res.Stdout(), "bar") && strings.Contains(res.Stdout(), "foo") { 324 found = 1 325 return poll.Success() 326 } 327 return poll.Continue("reading from journald is not yet finished") 328 } 329 poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second)) 330 assert.Equal(t, 1, found) 331 } 332 333 func TestRunWithJournaldLogDriverAndLogOpt(t *testing.T) { 334 if runtime.GOOS == "windows" { 335 t.Skip("journald log driver is not yet implemented on Windows") 336 } 337 base := testutil.NewBase(t) 338 containerName := testutil.Identifier(t) 339 340 defer base.Cmd("rm", "-f", containerName).AssertOK() 341 base.Cmd("run", "-d", "--log-driver", "journald", "--log-opt", "tag={{.FullID}}", "--name", containerName, testutil.CommonImage, 342 "sh", "-euxc", "echo foo; echo bar").AssertOK() 343 344 time.Sleep(3 * time.Second) 345 journalctl, err := exec.LookPath("journalctl") 346 assert.NilError(t, err) 347 inspectedContainer := base.InspectContainer(containerName) 348 found := 0 349 check := func(log poll.LogT) poll.Result { 350 res := icmd.RunCmd(icmd.Command(journalctl, "--no-pager", "--since", "2 minutes ago", fmt.Sprintf("SYSLOG_IDENTIFIER=%s", inspectedContainer.ID))) 351 assert.Equal(t, 0, res.ExitCode, res.Combined()) 352 if strings.Contains(res.Stdout(), "bar") && strings.Contains(res.Stdout(), "foo") { 353 found = 1 354 return poll.Success() 355 } 356 return poll.Continue("reading from journald is not yet finished") 357 } 358 poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second)) 359 assert.Equal(t, 1, found) 360 } 361 362 func TestRunWithLogBinary(t *testing.T) { 363 testutil.RequiresBuild(t) 364 if runtime.GOOS == "windows" { 365 t.Skip("buildkit is not enabled on windows, this feature may work on windows.") 366 } 367 testutil.DockerIncompatible(t) 368 t.Parallel() 369 base := testutil.NewBase(t) 370 imageName := testutil.Identifier(t) + "-image" 371 containerName := testutil.Identifier(t) 372 373 const dockerfile = ` 374 FROM golang:latest as builder 375 WORKDIR /go/src/ 376 RUN mkdir -p logger 377 WORKDIR /go/src/logger 378 RUN echo '\ 379 package main \n\ 380 \n\ 381 import ( \n\ 382 "bufio" \n\ 383 "context" \n\ 384 "fmt" \n\ 385 "io" \n\ 386 "os" \n\ 387 "path/filepath" \n\ 388 "sync" \n\ 389 \n\ 390 "github.com/containerd/containerd/runtime/v2/logging"\n\ 391 )\n\ 392 393 func main() {\n\ 394 logging.Run(log)\n\ 395 }\n\ 396 397 func log(ctx context.Context, config *logging.Config, ready func() error) error {\n\ 398 var wg sync.WaitGroup \n\ 399 wg.Add(2) \n\ 400 // forward both stdout and stderr to temp files \n\ 401 go copy(&wg, config.Stdout, config.ID, "stdout") \n\ 402 go copy(&wg, config.Stderr, config.ID, "stderr") \n\ 403 404 // signal that we are ready and setup for the container to be started \n\ 405 if err := ready(); err != nil { \n\ 406 return err \n\ 407 } \n\ 408 wg.Wait() \n\ 409 return nil \n\ 410 }\n\ 411 \n\ 412 func copy(wg *sync.WaitGroup, r io.Reader, id string, kind string) { \n\ 413 f, _ := os.Create(filepath.Join(os.TempDir(), fmt.Sprintf("%s_%s.log", id, kind))) \n\ 414 defer f.Close() \n\ 415 defer wg.Done() \n\ 416 s := bufio.NewScanner(r) \n\ 417 for s.Scan() { \n\ 418 f.WriteString(s.Text()) \n\ 419 } \n\ 420 }\n' >> main.go 421 422 423 RUN go mod init 424 RUN go mod tidy 425 RUN go build . 426 427 FROM scratch 428 COPY --from=builder /go/src/logger/logger / 429 ` 430 431 buildCtx, err := createBuildContext(dockerfile) 432 assert.NilError(t, err) 433 defer os.RemoveAll(buildCtx) 434 tmpDir := t.TempDir() 435 base.Cmd("build", buildCtx, "--output", fmt.Sprintf("type=local,src=/go/src/logger/logger,dest=%s", tmpDir)).AssertOK() 436 defer base.Cmd("image", "rm", "-f", imageName).AssertOK() 437 438 base.Cmd("container", "rm", "-f", containerName).AssertOK() 439 base.Cmd("run", "-d", "--log-driver", fmt.Sprintf("binary://%s/logger", tmpDir), "--name", containerName, testutil.CommonImage, 440 "sh", "-euxc", "echo foo; echo bar").AssertOK() 441 defer base.Cmd("container", "rm", "-f", containerName).AssertOK() 442 443 inspectedContainer := base.InspectContainer(containerName) 444 bytes, err := os.ReadFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s_%s.log", inspectedContainer.ID, "stdout"))) 445 assert.NilError(t, err) 446 log := string(bytes) 447 assert.Check(t, strings.Contains(log, "foo")) 448 assert.Check(t, strings.Contains(log, "bar")) 449 } 450 451 func TestRunWithTtyAndDetached(t *testing.T) { 452 if runtime.GOOS == "windows" { 453 t.Skip("json-file log driver is not yet implemented on Windows") 454 } 455 base := testutil.NewBase(t) 456 imageName := testutil.CommonImage 457 withoutTtyContainerName := "without-terminal-" + testutil.Identifier(t) 458 withTtyContainerName := "with-terminal-" + testutil.Identifier(t) 459 460 // without -t, fail 461 base.Cmd("run", "-d", "--name", withoutTtyContainerName, imageName, "stty").AssertOK() 462 defer base.Cmd("container", "rm", "-f", withoutTtyContainerName).AssertOK() 463 base.Cmd("logs", withoutTtyContainerName).AssertCombinedOutContains("stty: standard input: Not a tty") 464 withoutTtyContainer := base.InspectContainer(withoutTtyContainerName) 465 assert.Equal(base.T, 1, withoutTtyContainer.State.ExitCode) 466 467 // with -t, success 468 base.Cmd("run", "-d", "-t", "--name", withTtyContainerName, imageName, "stty").AssertOK() 469 defer base.Cmd("container", "rm", "-f", withTtyContainerName).AssertOK() 470 base.Cmd("logs", withTtyContainerName).AssertCombinedOutContains("speed 38400 baud; line = 0;") 471 withTtyContainer := base.InspectContainer(withTtyContainerName) 472 assert.Equal(base.T, 0, withTtyContainer.State.ExitCode) 473 } 474 475 // history: There was a bug that the --add-host items disappear when the another container created. 476 // This case ensures that it's doesn't happen. 477 // (https://github.com/containerd/nerdctl/issues/2560) 478 func TestRunAddHostRemainsWhenAnotherContainerCreated(t *testing.T) { 479 if runtime.GOOS == "windows" { 480 t.Skip("ocihook is not yet supported on Windows") 481 } 482 base := testutil.NewBase(t) 483 484 containerName := testutil.Identifier(t) 485 hostMapping := "test-add-host:10.0.0.1" 486 base.Cmd("run", "-d", "--add-host", hostMapping, "--name", containerName, testutil.CommonImage, "sleep", "infinity").AssertOK() 487 defer base.Cmd("container", "rm", "-f", containerName).Run() 488 489 checkEtcHosts := func(stdout string) error { 490 matcher, err := regexp.Compile(`^10.0.0.1\s+test-add-host$`) 491 if err != nil { 492 return err 493 } 494 var found bool 495 sc := bufio.NewScanner(bytes.NewBufferString(stdout)) 496 for sc.Scan() { 497 if matcher.Match(sc.Bytes()) { 498 found = true 499 } 500 } 501 if !found { 502 return fmt.Errorf("host not found") 503 } 504 return nil 505 } 506 base.Cmd("exec", containerName, "cat", "/etc/hosts").AssertOutWithFunc(checkEtcHosts) 507 508 // run another container 509 base.Cmd("run", "--rm", testutil.CommonImage).AssertOK() 510 511 base.Cmd("exec", containerName, "cat", "/etc/hosts").AssertOutWithFunc(checkEtcHosts) 512 } 513 514 // https://github.com/containerd/nerdctl/issues/2726 515 func TestRunRmTime(t *testing.T) { 516 base := testutil.NewBase(t) 517 base.Cmd("pull", testutil.CommonImage) 518 t0 := time.Now() 519 base.Cmd("run", "--rm", testutil.CommonImage, "true").AssertOK() 520 t1 := time.Now() 521 took := t1.Sub(t0) 522 const deadline = 3 * time.Second 523 if took > deadline { 524 t.Fatalf("expected to have completed in %v, took %v", deadline, took) 525 } 526 } 527 528 func runAttachStdin(t *testing.T, testStr string, args []string) string { 529 if runtime.GOOS == "windows" { 530 t.Skip("run attach test is not yet implemented on Windows") 531 } 532 533 t.Parallel() 534 base := testutil.NewBase(t) 535 containerName := testutil.Identifier(t) 536 537 opts := []func(*testutil.Cmd){ 538 testutil.WithStdin(strings.NewReader("echo " + testStr + "\nexit\n")), 539 } 540 541 fullArgs := []string{"run", "--rm", "-i"} 542 fullArgs = append(fullArgs, args...) 543 fullArgs = append(fullArgs, 544 "--name", 545 containerName, 546 testutil.CommonImage, 547 ) 548 549 defer base.Cmd("rm", "-f", containerName).AssertOK() 550 result := base.Cmd(fullArgs...).CmdOption(opts...).Run() 551 552 return result.Combined() 553 } 554 555 func runAttach(t *testing.T, testStr string, args []string) string { 556 if runtime.GOOS == "windows" { 557 t.Skip("run attach test is not yet implemented on Windows") 558 } 559 560 t.Parallel() 561 base := testutil.NewBase(t) 562 containerName := testutil.Identifier(t) 563 564 fullArgs := []string{"run"} 565 fullArgs = append(fullArgs, args...) 566 fullArgs = append(fullArgs, 567 "--name", 568 containerName, 569 testutil.CommonImage, 570 "sh", 571 "-euxc", 572 "echo "+testStr, 573 ) 574 575 defer base.Cmd("rm", "-f", containerName).AssertOK() 576 result := base.Cmd(fullArgs...).Run() 577 578 return result.Combined() 579 } 580 581 func TestRunAttachFlag(t *testing.T) { 582 583 type testCase struct { 584 name string 585 args []string 586 testFunc func(t *testing.T, testStr string, args []string) string 587 testStr string 588 expectedOut string 589 dockerOut string 590 } 591 testCases := []testCase{ 592 { 593 name: "AttachFlagStdin", 594 args: []string{"-a", "STDIN", "-a", "STDOUT"}, 595 testFunc: runAttachStdin, 596 testStr: "test-run-stdio", 597 expectedOut: "test-run-stdio", 598 dockerOut: "test-run-stdio", 599 }, 600 { 601 name: "AttachFlagStdOut", 602 args: []string{"-a", "STDOUT"}, 603 testFunc: runAttach, 604 testStr: "foo", 605 expectedOut: "foo", 606 dockerOut: "foo", 607 }, 608 { 609 name: "AttachFlagMixedValue", 610 args: []string{"-a", "STDIN", "-a", "invalid-value"}, 611 testFunc: runAttach, 612 testStr: "foo", 613 expectedOut: "invalid stream specified with -a flag. Valid streams are STDIN, STDOUT, and STDERR", 614 dockerOut: "valid streams are STDIN, STDOUT and STDERR", 615 }, 616 { 617 name: "AttachFlagInvalidValue", 618 args: []string{"-a", "invalid-stream"}, 619 testFunc: runAttach, 620 testStr: "foo", 621 expectedOut: "invalid stream specified with -a flag. Valid streams are STDIN, STDOUT, and STDERR", 622 dockerOut: "valid streams are STDIN, STDOUT and STDERR", 623 }, 624 { 625 name: "AttachFlagCaseInsensitive", 626 args: []string{"-a", "stdin", "-a", "stdout"}, 627 testFunc: runAttachStdin, 628 testStr: "test-run-stdio", 629 expectedOut: "test-run-stdio", 630 dockerOut: "test-run-stdio", 631 }, 632 } 633 634 for _, tc := range testCases { 635 tc := tc 636 t.Run(tc.name, func(t *testing.T) { 637 actualOut := tc.testFunc(t, tc.testStr, tc.args) 638 errorMsg := fmt.Sprintf("%s failed;\nExpected: '%s'\nActual: '%s'", tc.name, tc.expectedOut, actualOut) 639 if testutil.GetTarget() == testutil.Docker { 640 assert.Equal(t, true, strings.Contains(actualOut, tc.dockerOut), errorMsg) 641 } else { 642 assert.Equal(t, true, strings.Contains(actualOut, tc.expectedOut), errorMsg) 643 } 644 }) 645 } 646 }