github.com/docker/docker-ce@v17.12.1-ce-rc2+incompatible/components/engine/integration-cli/docker_cli_exec_test.go (about) 1 // +build !test_no_exec 2 3 package main 4 5 import ( 6 "bufio" 7 "fmt" 8 "os" 9 "os/exec" 10 "reflect" 11 "runtime" 12 "sort" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/docker/docker/client" 18 "github.com/docker/docker/integration-cli/checker" 19 "github.com/docker/docker/integration-cli/cli" 20 "github.com/docker/docker/integration-cli/cli/build" 21 "github.com/go-check/check" 22 "github.com/gotestyourself/gotestyourself/icmd" 23 "golang.org/x/net/context" 24 ) 25 26 func (s *DockerSuite) TestExec(c *check.C) { 27 testRequires(c, DaemonIsLinux) 28 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 29 c.Assert(waitRun(strings.TrimSpace(out)), check.IsNil) 30 31 out, _ = dockerCmd(c, "exec", "testing", "cat", "/tmp/file") 32 out = strings.Trim(out, "\r\n") 33 c.Assert(out, checker.Equals, "test") 34 35 } 36 37 func (s *DockerSuite) TestExecInteractive(c *check.C) { 38 testRequires(c, DaemonIsLinux) 39 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 40 41 execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") 42 stdin, err := execCmd.StdinPipe() 43 c.Assert(err, checker.IsNil) 44 stdout, err := execCmd.StdoutPipe() 45 c.Assert(err, checker.IsNil) 46 47 err = execCmd.Start() 48 c.Assert(err, checker.IsNil) 49 _, err = stdin.Write([]byte("cat /tmp/file\n")) 50 c.Assert(err, checker.IsNil) 51 52 r := bufio.NewReader(stdout) 53 line, err := r.ReadString('\n') 54 c.Assert(err, checker.IsNil) 55 line = strings.TrimSpace(line) 56 c.Assert(line, checker.Equals, "test") 57 err = stdin.Close() 58 c.Assert(err, checker.IsNil) 59 errChan := make(chan error) 60 go func() { 61 errChan <- execCmd.Wait() 62 close(errChan) 63 }() 64 select { 65 case err := <-errChan: 66 c.Assert(err, checker.IsNil) 67 case <-time.After(1 * time.Second): 68 c.Fatal("docker exec failed to exit on stdin close") 69 } 70 71 } 72 73 func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { 74 out := runSleepingContainer(c) 75 cleanedContainerID := strings.TrimSpace(out) 76 c.Assert(waitRun(cleanedContainerID), check.IsNil) 77 dockerCmd(c, "restart", cleanedContainerID) 78 c.Assert(waitRun(cleanedContainerID), check.IsNil) 79 80 out, _ = dockerCmd(c, "exec", cleanedContainerID, "echo", "hello") 81 outStr := strings.TrimSpace(out) 82 c.Assert(outStr, checker.Equals, "hello") 83 } 84 85 func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) { 86 // TODO Windows CI: Requires a little work to get this ported. 87 testRequires(c, DaemonIsLinux, SameHostDaemon) 88 s.d.StartWithBusybox(c) 89 90 out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top") 91 c.Assert(err, checker.IsNil, check.Commentf("Could not run top: %s", out)) 92 93 s.d.Restart(c) 94 95 out, err = s.d.Cmd("start", "top") 96 c.Assert(err, checker.IsNil, check.Commentf("Could not start top after daemon restart: %s", out)) 97 98 out, err = s.d.Cmd("exec", "top", "echo", "hello") 99 c.Assert(err, checker.IsNil, check.Commentf("Could not exec on container top: %s", out)) 100 101 outStr := strings.TrimSpace(string(out)) 102 c.Assert(outStr, checker.Equals, "hello") 103 } 104 105 // Regression test for #9155, #9044 106 func (s *DockerSuite) TestExecEnv(c *check.C) { 107 // TODO Windows CI: This one is interesting and may just end up being a feature 108 // difference between Windows and Linux. On Windows, the environment is passed 109 // into the process that is launched, not into the machine environment. Hence 110 // a subsequent exec will not have LALA set/ 111 testRequires(c, DaemonIsLinux) 112 runSleepingContainer(c, "-e", "LALA=value1", "-e", "LALA=value2", "-d", "--name", "testing") 113 c.Assert(waitRun("testing"), check.IsNil) 114 115 out, _ := dockerCmd(c, "exec", "testing", "env") 116 c.Assert(out, checker.Not(checker.Contains), "LALA=value1") 117 c.Assert(out, checker.Contains, "LALA=value2") 118 c.Assert(out, checker.Contains, "HOME=/root") 119 } 120 121 func (s *DockerSuite) TestExecSetEnv(c *check.C) { 122 testRequires(c, DaemonIsLinux) 123 runSleepingContainer(c, "-e", "HOME=/root", "-d", "--name", "testing") 124 c.Assert(waitRun("testing"), check.IsNil) 125 126 out, _ := dockerCmd(c, "exec", "-e", "HOME=/another", "-e", "ABC=xyz", "testing", "env") 127 c.Assert(out, checker.Not(checker.Contains), "HOME=/root") 128 c.Assert(out, checker.Contains, "HOME=/another") 129 c.Assert(out, checker.Contains, "ABC=xyz") 130 } 131 132 func (s *DockerSuite) TestExecExitStatus(c *check.C) { 133 runSleepingContainer(c, "-d", "--name", "top") 134 135 result := icmd.RunCommand(dockerBinary, "exec", "top", "sh", "-c", "exit 23") 136 result.Assert(c, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) 137 } 138 139 func (s *DockerSuite) TestExecPausedContainer(c *check.C) { 140 testRequires(c, IsPausable) 141 142 out := runSleepingContainer(c, "-d", "--name", "testing") 143 ContainerID := strings.TrimSpace(out) 144 145 dockerCmd(c, "pause", "testing") 146 out, _, err := dockerCmdWithError("exec", ContainerID, "echo", "hello") 147 c.Assert(err, checker.NotNil, check.Commentf("container should fail to exec new command if it is paused")) 148 149 expected := ContainerID + " is paused, unpause the container before exec" 150 c.Assert(out, checker.Contains, expected, check.Commentf("container should not exec new command if it is paused")) 151 } 152 153 // regression test for #9476 154 func (s *DockerSuite) TestExecTTYCloseStdin(c *check.C) { 155 // TODO Windows CI: This requires some work to port to Windows. 156 testRequires(c, DaemonIsLinux) 157 dockerCmd(c, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") 158 159 cmd := exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") 160 stdinRw, err := cmd.StdinPipe() 161 c.Assert(err, checker.IsNil) 162 163 stdinRw.Write([]byte("test")) 164 stdinRw.Close() 165 166 out, _, err := runCommandWithOutput(cmd) 167 c.Assert(err, checker.IsNil, check.Commentf(out)) 168 169 out, _ = dockerCmd(c, "top", "exec_tty_stdin") 170 outArr := strings.Split(out, "\n") 171 c.Assert(len(outArr), checker.LessOrEqualThan, 3, check.Commentf("exec process left running")) 172 c.Assert(out, checker.Not(checker.Contains), "nsenter-exec") 173 } 174 175 func (s *DockerSuite) TestExecTTYWithoutStdin(c *check.C) { 176 out, _ := dockerCmd(c, "run", "-d", "-ti", "busybox") 177 id := strings.TrimSpace(out) 178 c.Assert(waitRun(id), checker.IsNil) 179 180 errChan := make(chan error) 181 go func() { 182 defer close(errChan) 183 184 cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") 185 if _, err := cmd.StdinPipe(); err != nil { 186 errChan <- err 187 return 188 } 189 190 expected := "the input device is not a TTY" 191 if runtime.GOOS == "windows" { 192 expected += ". If you are using mintty, try prefixing the command with 'winpty'" 193 } 194 if out, _, err := runCommandWithOutput(cmd); err == nil { 195 errChan <- fmt.Errorf("exec should have failed") 196 return 197 } else if !strings.Contains(out, expected) { 198 errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) 199 return 200 } 201 }() 202 203 select { 204 case err := <-errChan: 205 c.Assert(err, check.IsNil) 206 case <-time.After(3 * time.Second): 207 c.Fatal("exec is running but should have failed") 208 } 209 } 210 211 // FIXME(vdemeester) this should be a unit tests on cli/command/container package 212 func (s *DockerSuite) TestExecParseError(c *check.C) { 213 // TODO Windows CI: Requires some extra work. Consider copying the 214 // runSleepingContainer helper to have an exec version. 215 testRequires(c, DaemonIsLinux) 216 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 217 218 // Test normal (non-detached) case first 219 icmd.RunCommand(dockerBinary, "exec", "top").Assert(c, icmd.Expected{ 220 ExitCode: 1, 221 Error: "exit status 1", 222 Err: "See 'docker exec --help'", 223 }) 224 } 225 226 func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { 227 // TODO Windows CI: Requires some extra work. Consider copying the 228 // runSleepingContainer helper to have an exec version. 229 testRequires(c, DaemonIsLinux) 230 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 231 232 result := icmd.StartCmd(icmd.Command(dockerBinary, "exec", "testing", "top")) 233 result.Assert(c, icmd.Success) 234 go icmd.WaitOnCmd(0, result) 235 236 type dstop struct { 237 out string 238 err error 239 } 240 ch := make(chan dstop) 241 go func() { 242 result := icmd.RunCommand(dockerBinary, "stop", "testing") 243 ch <- dstop{result.Combined(), result.Error} 244 close(ch) 245 }() 246 select { 247 case <-time.After(3 * time.Second): 248 c.Fatal("Container stop timed out") 249 case s := <-ch: 250 c.Assert(s.err, check.IsNil) 251 } 252 } 253 254 func (s *DockerSuite) TestExecCgroup(c *check.C) { 255 // Not applicable on Windows - using Linux specific functionality 256 testRequires(c, NotUserNamespace) 257 testRequires(c, DaemonIsLinux) 258 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 259 260 out, _ := dockerCmd(c, "exec", "testing", "cat", "/proc/1/cgroup") 261 containerCgroups := sort.StringSlice(strings.Split(out, "\n")) 262 263 var wg sync.WaitGroup 264 var mu sync.Mutex 265 execCgroups := []sort.StringSlice{} 266 errChan := make(chan error) 267 // exec a few times concurrently to get consistent failure 268 for i := 0; i < 5; i++ { 269 wg.Add(1) 270 go func() { 271 out, _, err := dockerCmdWithError("exec", "testing", "cat", "/proc/self/cgroup") 272 if err != nil { 273 errChan <- err 274 return 275 } 276 cg := sort.StringSlice(strings.Split(out, "\n")) 277 278 mu.Lock() 279 execCgroups = append(execCgroups, cg) 280 mu.Unlock() 281 wg.Done() 282 }() 283 } 284 wg.Wait() 285 close(errChan) 286 287 for err := range errChan { 288 c.Assert(err, checker.IsNil) 289 } 290 291 for _, cg := range execCgroups { 292 if !reflect.DeepEqual(cg, containerCgroups) { 293 fmt.Println("exec cgroups:") 294 for _, name := range cg { 295 fmt.Printf(" %s\n", name) 296 } 297 298 fmt.Println("container cgroups:") 299 for _, name := range containerCgroups { 300 fmt.Printf(" %s\n", name) 301 } 302 c.Fatal("cgroups mismatched") 303 } 304 } 305 } 306 307 func (s *DockerSuite) TestExecInspectID(c *check.C) { 308 out := runSleepingContainer(c, "-d") 309 id := strings.TrimSuffix(out, "\n") 310 311 out = inspectField(c, id, "ExecIDs") 312 c.Assert(out, checker.Equals, "[]", check.Commentf("ExecIDs should be empty, got: %s", out)) 313 314 // Start an exec, have it block waiting so we can do some checking 315 cmd := exec.Command(dockerBinary, "exec", id, "sh", "-c", 316 "while ! test -e /execid1; do sleep 1; done") 317 318 err := cmd.Start() 319 c.Assert(err, checker.IsNil, check.Commentf("failed to start the exec cmd")) 320 321 // Give the exec 10 chances/seconds to start then give up and stop the test 322 tries := 10 323 for i := 0; i < tries; i++ { 324 // Since its still running we should see exec as part of the container 325 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 326 327 if out != "[]" && out != "<no value>" { 328 break 329 } 330 c.Assert(i+1, checker.Not(checker.Equals), tries, check.Commentf("ExecIDs still empty after 10 second")) 331 time.Sleep(1 * time.Second) 332 } 333 334 // Save execID for later 335 execID, err := inspectFilter(id, "index .ExecIDs 0") 336 c.Assert(err, checker.IsNil, check.Commentf("failed to get the exec id")) 337 338 // End the exec by creating the missing file 339 err = exec.Command(dockerBinary, "exec", id, 340 "sh", "-c", "touch /execid1").Run() 341 342 c.Assert(err, checker.IsNil, check.Commentf("failed to run the 2nd exec cmd")) 343 344 // Wait for 1st exec to complete 345 cmd.Wait() 346 347 // Give the exec 10 chances/seconds to stop then give up and stop the test 348 for i := 0; i < tries; i++ { 349 // Since its still running we should see exec as part of the container 350 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 351 352 if out == "[]" { 353 break 354 } 355 c.Assert(i+1, checker.Not(checker.Equals), tries, check.Commentf("ExecIDs still not empty after 10 second")) 356 time.Sleep(1 * time.Second) 357 } 358 359 // But we should still be able to query the execID 360 cli, err := client.NewEnvClient() 361 c.Assert(err, checker.IsNil) 362 defer cli.Close() 363 364 _, err = cli.ContainerExecInspect(context.Background(), execID) 365 c.Assert(err, checker.IsNil) 366 367 // Now delete the container and then an 'inspect' on the exec should 368 // result in a 404 (not 'container not running') 369 out, ec := dockerCmd(c, "rm", "-f", id) 370 c.Assert(ec, checker.Equals, 0, check.Commentf("error removing container: %s", out)) 371 372 _, err = cli.ContainerExecInspect(context.Background(), execID) 373 expected := "No such exec instance" 374 c.Assert(err.Error(), checker.Contains, expected) 375 } 376 377 func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { 378 // Problematic on Windows as Windows does not support links 379 testRequires(c, DaemonIsLinux) 380 var out string 381 out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") 382 idA := strings.TrimSpace(out) 383 c.Assert(idA, checker.Not(checker.Equals), "", check.Commentf("%s, id should not be nil", out)) 384 out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") 385 idB := strings.TrimSpace(out) 386 c.Assert(idB, checker.Not(checker.Equals), "", check.Commentf("%s, id should not be nil", out)) 387 388 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 389 dockerCmd(c, "rename", "container1", "container_new") 390 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 391 } 392 393 func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { 394 // Not applicable on Windows to Windows CI. 395 testRequires(c, SameHostDaemon, DaemonIsLinux) 396 for _, fn := range []string{"resolv.conf", "hosts"} { 397 containers := cli.DockerCmd(c, "ps", "-q", "-a").Combined() 398 if containers != "" { 399 cli.DockerCmd(c, append([]string{"rm", "-fv"}, strings.Split(strings.TrimSpace(containers), "\n")...)...) 400 } 401 402 content := runCommandAndReadContainerFile(c, fn, dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn)) 403 404 c.Assert(strings.TrimSpace(string(content)), checker.Equals, "success", check.Commentf("Content was not what was modified in the container", string(content))) 405 406 out, _ := dockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top") 407 contID := strings.TrimSpace(out) 408 netFilePath := containerStorageFile(contID, fn) 409 410 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) 411 c.Assert(err, checker.IsNil) 412 413 if _, err := f.Seek(0, 0); err != nil { 414 f.Close() 415 c.Fatal(err) 416 } 417 418 if err := f.Truncate(0); err != nil { 419 f.Close() 420 c.Fatal(err) 421 } 422 423 if _, err := f.Write([]byte("success2\n")); err != nil { 424 f.Close() 425 c.Fatal(err) 426 } 427 f.Close() 428 429 res, _ := dockerCmd(c, "exec", contID, "cat", "/etc/"+fn) 430 c.Assert(res, checker.Equals, "success2\n") 431 } 432 } 433 434 func (s *DockerSuite) TestExecWithUser(c *check.C) { 435 // TODO Windows CI: This may be fixable in the future once Windows 436 // supports users 437 testRequires(c, DaemonIsLinux) 438 dockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 439 440 out, _ := dockerCmd(c, "exec", "-u", "1", "parent", "id") 441 c.Assert(out, checker.Contains, "uid=1(daemon) gid=1(daemon)") 442 443 out, _ = dockerCmd(c, "exec", "-u", "root", "parent", "id") 444 c.Assert(out, checker.Contains, "uid=0(root) gid=0(root)", check.Commentf("exec with user by id expected daemon user got %s", out)) 445 } 446 447 func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { 448 // Not applicable on Windows 449 testRequires(c, DaemonIsLinux, NotUserNamespace) 450 // Start main loop which attempts mknod repeatedly 451 dockerCmd(c, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "sh", "-c", `while (true); do if [ -e /exec_priv ]; then cat /exec_priv && mknod /tmp/sda b 8 0 && echo "Success"; else echo "Privileged exec has not run yet"; fi; usleep 10000; done`) 452 453 // Check exec mknod doesn't work 454 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16").Assert(c, icmd.Expected{ 455 ExitCode: 1, 456 Err: "Operation not permitted", 457 }) 458 459 // Check exec mknod does work with --privileged 460 result := icmd.RunCommand(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", `echo "Running exec --privileged" > /exec_priv && mknod /tmp/sdb b 8 16 && usleep 50000 && echo "Finished exec --privileged" > /exec_priv && echo ok`) 461 result.Assert(c, icmd.Success) 462 463 actual := strings.TrimSpace(result.Combined()) 464 c.Assert(actual, checker.Equals, "ok", check.Commentf("exec mknod in --cap-drop=ALL container with --privileged failed, output: %q", result.Combined())) 465 466 // Check subsequent unprivileged exec cannot mknod 467 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32").Assert(c, icmd.Expected{ 468 ExitCode: 1, 469 Err: "Operation not permitted", 470 }) 471 // Confirm at no point was mknod allowed 472 result = icmd.RunCommand(dockerBinary, "logs", "parent") 473 result.Assert(c, icmd.Success) 474 c.Assert(result.Combined(), checker.Not(checker.Contains), "Success") 475 476 } 477 478 func (s *DockerSuite) TestExecWithImageUser(c *check.C) { 479 // Not applicable on Windows 480 testRequires(c, DaemonIsLinux) 481 name := "testbuilduser" 482 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 483 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 484 USER dockerio`)) 485 dockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 486 487 out, _ := dockerCmd(c, "exec", "dockerioexec", "whoami") 488 c.Assert(out, checker.Contains, "dockerio", check.Commentf("exec with user by id expected dockerio user got %s", out)) 489 } 490 491 func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) { 492 // Windows does not support read-only 493 // --read-only + userns has remount issues 494 testRequires(c, DaemonIsLinux, NotUserNamespace) 495 dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 496 dockerCmd(c, "exec", "parent", "true") 497 } 498 499 func (s *DockerSuite) TestExecUlimits(c *check.C) { 500 testRequires(c, DaemonIsLinux) 501 name := "testexeculimits" 502 runSleepingContainer(c, "-d", "--ulimit", "nofile=511:511", "--name", name) 503 c.Assert(waitRun(name), checker.IsNil) 504 505 out, _, err := dockerCmdWithError("exec", name, "sh", "-c", "ulimit -n") 506 c.Assert(err, checker.IsNil) 507 c.Assert(strings.TrimSpace(out), checker.Equals, "511") 508 } 509 510 // #15750 511 func (s *DockerSuite) TestExecStartFails(c *check.C) { 512 // TODO Windows CI. This test should be portable. Figure out why it fails 513 // currently. 514 testRequires(c, DaemonIsLinux) 515 name := "exec-15750" 516 runSleepingContainer(c, "-d", "--name", name) 517 c.Assert(waitRun(name), checker.IsNil) 518 519 out, _, err := dockerCmdWithError("exec", name, "no-such-cmd") 520 c.Assert(err, checker.NotNil, check.Commentf(out)) 521 c.Assert(out, checker.Contains, "executable file not found") 522 } 523 524 // Fix regression in https://github.com/docker/docker/pull/26461#issuecomment-250287297 525 func (s *DockerSuite) TestExecWindowsPathNotWiped(c *check.C) { 526 testRequires(c, DaemonIsWindows) 527 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", minimalBaseImage(), "powershell", "start-sleep", "60") 528 c.Assert(waitRun(strings.TrimSpace(out)), check.IsNil) 529 530 out, _ = dockerCmd(c, "exec", "testing", "powershell", "write-host", "$env:PATH") 531 out = strings.ToLower(strings.Trim(out, "\r\n")) 532 c.Assert(out, checker.Contains, `windowspowershell\v1.0`) 533 } 534 535 func (s *DockerSuite) TestExecEnvLinksHost(c *check.C) { 536 testRequires(c, DaemonIsLinux) 537 runSleepingContainer(c, "-d", "--name", "foo") 538 runSleepingContainer(c, "-d", "--link", "foo:db", "--hostname", "myhost", "--name", "bar") 539 out, _ := dockerCmd(c, "exec", "bar", "env") 540 c.Assert(out, checker.Contains, "HOSTNAME=myhost") 541 c.Assert(out, checker.Contains, "DB_NAME=/bar/db") 542 } 543 544 func (s *DockerSuite) TestExecWindowsOpenHandles(c *check.C) { 545 testRequires(c, DaemonIsWindows) 546 runSleepingContainer(c, "-d", "--name", "test") 547 exec := make(chan bool) 548 go func() { 549 dockerCmd(c, "exec", "test", "cmd", "/c", "start sleep 10") 550 exec <- true 551 }() 552 553 count := 0 554 for { 555 top := make(chan string) 556 var out string 557 go func() { 558 out, _ := dockerCmd(c, "top", "test") 559 top <- out 560 }() 561 562 select { 563 case <-time.After(time.Second * 5): 564 c.Fatal("timed out waiting for top while exec is exiting") 565 case out = <-top: 566 break 567 } 568 569 if strings.Count(out, "busybox.exe") == 2 && !strings.Contains(out, "cmd.exe") { 570 // The initial exec process (cmd.exe) has exited, and both sleeps are currently running 571 break 572 } 573 count++ 574 if count >= 30 { 575 c.Fatal("too many retries") 576 } 577 time.Sleep(1 * time.Second) 578 } 579 580 inspect := make(chan bool) 581 go func() { 582 dockerCmd(c, "inspect", "test") 583 inspect <- true 584 }() 585 586 select { 587 case <-time.After(time.Second * 5): 588 c.Fatal("timed out waiting for inspect while exec is exiting") 589 case <-inspect: 590 break 591 } 592 593 // Ensure the background sleep is still running 594 out, _ := dockerCmd(c, "top", "test") 595 c.Assert(strings.Count(out, "busybox.exe"), checker.Equals, 2) 596 597 // The exec should exit when the background sleep exits 598 select { 599 case <-time.After(time.Second * 15): 600 c.Fatal("timed out waiting for async exec to exit") 601 case <-exec: 602 // Ensure the background sleep has actually exited 603 out, _ := dockerCmd(c, "top", "test") 604 c.Assert(strings.Count(out, "busybox.exe"), checker.Equals, 1) 605 break 606 } 607 }