github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/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 "net/http" 9 "os" 10 "os/exec" 11 "reflect" 12 "runtime" 13 "sort" 14 "strings" 15 "sync" 16 "time" 17 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/docker/docker/integration-cli/request" 22 icmd "github.com/docker/docker/pkg/testutil/cmd" 23 "github.com/go-check/check" 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 c.Assert(result, icmd.Matches, 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 err := exec.Command(dockerBinary, "exec", "testing", "top").Start() 233 c.Assert(err, checker.IsNil) 234 235 type dstop struct { 236 out []byte 237 err error 238 } 239 240 ch := make(chan dstop) 241 go func() { 242 out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput() 243 ch <- dstop{out, err} 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 sc, body, _ := request.SockRequest("GET", "/exec/"+execID+"/json", nil, daemonHost()) 361 362 c.Assert(sc, checker.Equals, http.StatusOK, check.Commentf("received status != 200 OK: %d\n%s", sc, body)) 363 364 // Now delete the container and then an 'inspect' on the exec should 365 // result in a 404 (not 'container not running') 366 out, ec := dockerCmd(c, "rm", "-f", id) 367 c.Assert(ec, checker.Equals, 0, check.Commentf("error removing container: %s", out)) 368 sc, body, _ = request.SockRequest("GET", "/exec/"+execID+"/json", nil, daemonHost()) 369 c.Assert(sc, checker.Equals, http.StatusNotFound, check.Commentf("received status != 404: %d\n%s", sc, body)) 370 } 371 372 func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { 373 // Problematic on Windows as Windows does not support links 374 testRequires(c, DaemonIsLinux) 375 var out string 376 out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") 377 idA := strings.TrimSpace(out) 378 c.Assert(idA, checker.Not(checker.Equals), "", check.Commentf("%s, id should not be nil", out)) 379 out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") 380 idB := strings.TrimSpace(out) 381 c.Assert(idB, checker.Not(checker.Equals), "", check.Commentf("%s, id should not be nil", out)) 382 383 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 384 dockerCmd(c, "rename", "container1", "container_new") 385 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 386 } 387 388 func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { 389 // Not applicable on Windows to Windows CI. 390 testRequires(c, SameHostDaemon, DaemonIsLinux) 391 for _, fn := range []string{"resolv.conf", "hosts"} { 392 containers := cli.DockerCmd(c, "ps", "-q", "-a").Combined() 393 if containers != "" { 394 cli.DockerCmd(c, append([]string{"rm", "-fv"}, strings.Split(strings.TrimSpace(containers), "\n")...)...) 395 } 396 397 content := runCommandAndReadContainerFile(c, fn, dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn)) 398 399 c.Assert(strings.TrimSpace(string(content)), checker.Equals, "success", check.Commentf("Content was not what was modified in the container", string(content))) 400 401 out, _ := dockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top") 402 contID := strings.TrimSpace(out) 403 netFilePath := containerStorageFile(contID, fn) 404 405 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) 406 c.Assert(err, checker.IsNil) 407 408 if _, err := f.Seek(0, 0); err != nil { 409 f.Close() 410 c.Fatal(err) 411 } 412 413 if err := f.Truncate(0); err != nil { 414 f.Close() 415 c.Fatal(err) 416 } 417 418 if _, err := f.Write([]byte("success2\n")); err != nil { 419 f.Close() 420 c.Fatal(err) 421 } 422 f.Close() 423 424 res, _ := dockerCmd(c, "exec", contID, "cat", "/etc/"+fn) 425 c.Assert(res, checker.Equals, "success2\n") 426 } 427 } 428 429 func (s *DockerSuite) TestExecWithUser(c *check.C) { 430 // TODO Windows CI: This may be fixable in the future once Windows 431 // supports users 432 testRequires(c, DaemonIsLinux) 433 dockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 434 435 out, _ := dockerCmd(c, "exec", "-u", "1", "parent", "id") 436 c.Assert(out, checker.Contains, "uid=1(daemon) gid=1(daemon)") 437 438 out, _ = dockerCmd(c, "exec", "-u", "root", "parent", "id") 439 c.Assert(out, checker.Contains, "uid=0(root) gid=0(root)", check.Commentf("exec with user by id expected daemon user got %s", out)) 440 } 441 442 func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { 443 // Not applicable on Windows 444 testRequires(c, DaemonIsLinux, NotUserNamespace) 445 // Start main loop which attempts mknod repeatedly 446 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`) 447 448 // Check exec mknod doesn't work 449 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16").Assert(c, icmd.Expected{ 450 ExitCode: 1, 451 Err: "Operation not permitted", 452 }) 453 454 // Check exec mknod does work with --privileged 455 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`) 456 result.Assert(c, icmd.Success) 457 458 actual := strings.TrimSpace(result.Combined()) 459 c.Assert(actual, checker.Equals, "ok", check.Commentf("exec mknod in --cap-drop=ALL container with --privileged failed, output: %q", result.Combined())) 460 461 // Check subsequent unprivileged exec cannot mknod 462 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32").Assert(c, icmd.Expected{ 463 ExitCode: 1, 464 Err: "Operation not permitted", 465 }) 466 // Confirm at no point was mknod allowed 467 result = icmd.RunCommand(dockerBinary, "logs", "parent") 468 result.Assert(c, icmd.Success) 469 c.Assert(result.Combined(), checker.Not(checker.Contains), "Success") 470 471 } 472 473 func (s *DockerSuite) TestExecWithImageUser(c *check.C) { 474 // Not applicable on Windows 475 testRequires(c, DaemonIsLinux) 476 name := "testbuilduser" 477 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 478 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 479 USER dockerio`)) 480 dockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 481 482 out, _ := dockerCmd(c, "exec", "dockerioexec", "whoami") 483 c.Assert(out, checker.Contains, "dockerio", check.Commentf("exec with user by id expected dockerio user got %s", out)) 484 } 485 486 func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) { 487 // Windows does not support read-only 488 // --read-only + userns has remount issues 489 testRequires(c, DaemonIsLinux, NotUserNamespace) 490 dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 491 dockerCmd(c, "exec", "parent", "true") 492 } 493 494 func (s *DockerSuite) TestExecUlimits(c *check.C) { 495 testRequires(c, DaemonIsLinux) 496 name := "testexeculimits" 497 runSleepingContainer(c, "-d", "--ulimit", "nofile=511:511", "--name", name) 498 c.Assert(waitRun(name), checker.IsNil) 499 500 out, _, err := dockerCmdWithError("exec", name, "sh", "-c", "ulimit -n") 501 c.Assert(err, checker.IsNil) 502 c.Assert(strings.TrimSpace(out), checker.Equals, "511") 503 } 504 505 // #15750 506 func (s *DockerSuite) TestExecStartFails(c *check.C) { 507 // TODO Windows CI. This test should be portable. Figure out why it fails 508 // currently. 509 testRequires(c, DaemonIsLinux) 510 name := "exec-15750" 511 runSleepingContainer(c, "-d", "--name", name) 512 c.Assert(waitRun(name), checker.IsNil) 513 514 out, _, err := dockerCmdWithError("exec", name, "no-such-cmd") 515 c.Assert(err, checker.NotNil, check.Commentf(out)) 516 c.Assert(out, checker.Contains, "executable file not found") 517 } 518 519 // Fix regression in https://github.com/docker/docker/pull/26461#issuecomment-250287297 520 func (s *DockerSuite) TestExecWindowsPathNotWiped(c *check.C) { 521 testRequires(c, DaemonIsWindows) 522 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", minimalBaseImage(), "powershell", "start-sleep", "60") 523 c.Assert(waitRun(strings.TrimSpace(out)), check.IsNil) 524 525 out, _ = dockerCmd(c, "exec", "testing", "powershell", "write-host", "$env:PATH") 526 out = strings.ToLower(strings.Trim(out, "\r\n")) 527 c.Assert(out, checker.Contains, `windowspowershell\v1.0`) 528 } 529 530 func (s *DockerSuite) TestExecEnvLinksHost(c *check.C) { 531 testRequires(c, DaemonIsLinux) 532 runSleepingContainer(c, "-d", "--name", "foo") 533 runSleepingContainer(c, "-d", "--link", "foo:db", "--hostname", "myhost", "--name", "bar") 534 out, _ := dockerCmd(c, "exec", "bar", "env") 535 c.Assert(out, checker.Contains, "HOSTNAME=myhost") 536 c.Assert(out, checker.Contains, "DB_NAME=/bar/db") 537 } 538 539 func (s *DockerSuite) TestExecWindowsOpenHandles(c *check.C) { 540 testRequires(c, DaemonIsWindows) 541 runSleepingContainer(c, "-d", "--name", "test") 542 exec := make(chan bool) 543 go func() { 544 dockerCmd(c, "exec", "test", "cmd", "/c", "start sleep 10") 545 exec <- true 546 }() 547 548 count := 0 549 for { 550 top := make(chan string) 551 var out string 552 go func() { 553 out, _ := dockerCmd(c, "top", "test") 554 top <- out 555 }() 556 557 select { 558 case <-time.After(time.Second * 5): 559 c.Fatal("timed out waiting for top while exec is exiting") 560 case out = <-top: 561 break 562 } 563 564 if strings.Count(out, "busybox.exe") == 2 && !strings.Contains(out, "cmd.exe") { 565 // The initial exec process (cmd.exe) has exited, and both sleeps are currently running 566 break 567 } 568 count++ 569 if count >= 30 { 570 c.Fatal("too many retries") 571 } 572 time.Sleep(1 * time.Second) 573 } 574 575 inspect := make(chan bool) 576 go func() { 577 dockerCmd(c, "inspect", "test") 578 inspect <- true 579 }() 580 581 select { 582 case <-time.After(time.Second * 5): 583 c.Fatal("timed out waiting for inspect while exec is exiting") 584 case <-inspect: 585 break 586 } 587 588 // Ensure the background sleep is still running 589 out, _ := dockerCmd(c, "top", "test") 590 c.Assert(strings.Count(out, "busybox.exe"), checker.Equals, 2) 591 592 // The exec should exit when the background sleep exits 593 select { 594 case <-time.After(time.Second * 15): 595 c.Fatal("timed out waiting for async exec to exit") 596 case <-exec: 597 // Ensure the background sleep has actually exited 598 out, _ := dockerCmd(c, "top", "test") 599 c.Assert(strings.Count(out, "busybox.exe"), checker.Equals, 1) 600 break 601 } 602 }