wait_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package container // import "github.com/docker/docker/integration/container"
  2. import (
  3. "testing"
  4. "time"
  5. containertypes "github.com/docker/docker/api/types/container"
  6. "github.com/docker/docker/integration/internal/container"
  7. "github.com/docker/docker/testutil"
  8. "github.com/docker/docker/testutil/request"
  9. "gotest.tools/v3/assert"
  10. is "gotest.tools/v3/assert/cmp"
  11. "gotest.tools/v3/poll"
  12. "gotest.tools/v3/skip"
  13. )
  14. func TestWaitNonBlocked(t *testing.T) {
  15. ctx := setupTest(t)
  16. cli := request.NewAPIClient(t)
  17. testCases := []struct {
  18. doc string
  19. cmd string
  20. expectedCode int64
  21. }{
  22. {
  23. doc: "wait-nonblocking-exit-0",
  24. cmd: "exit 0",
  25. expectedCode: 0,
  26. },
  27. {
  28. doc: "wait-nonblocking-exit-random",
  29. cmd: "exit 99",
  30. expectedCode: 99,
  31. },
  32. }
  33. for _, tc := range testCases {
  34. tc := tc
  35. t.Run(tc.doc, func(t *testing.T) {
  36. t.Parallel()
  37. ctx := testutil.StartSpan(ctx, t)
  38. containerID := container.Run(ctx, t, cli, container.WithCmd("sh", "-c", tc.cmd))
  39. poll.WaitOn(t, container.IsInState(ctx, cli, containerID, "exited"), poll.WithTimeout(30*time.Second), poll.WithDelay(100*time.Millisecond))
  40. waitResC, errC := cli.ContainerWait(ctx, containerID, "")
  41. select {
  42. case err := <-errC:
  43. assert.NilError(t, err)
  44. case waitRes := <-waitResC:
  45. assert.Check(t, is.Equal(tc.expectedCode, waitRes.StatusCode))
  46. }
  47. })
  48. }
  49. }
  50. func TestWaitBlocked(t *testing.T) {
  51. // Windows busybox does not support trap in this way, not sleep with sub-second
  52. // granularity. It will always exit 0x40010004.
  53. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  54. ctx := setupTest(t)
  55. cli := request.NewAPIClient(t)
  56. testCases := []struct {
  57. doc string
  58. cmd string
  59. expectedCode int64
  60. }{
  61. {
  62. doc: "test-wait-blocked-exit-zero",
  63. cmd: "trap 'exit 0' TERM; while true; do usleep 10; done",
  64. expectedCode: 0,
  65. },
  66. {
  67. doc: "test-wait-blocked-exit-random",
  68. cmd: "trap 'exit 99' TERM; while true; do usleep 10; done",
  69. expectedCode: 99,
  70. },
  71. }
  72. for _, tc := range testCases {
  73. tc := tc
  74. t.Run(tc.doc, func(t *testing.T) {
  75. t.Parallel()
  76. ctx := testutil.StartSpan(ctx, t)
  77. containerID := container.Run(ctx, t, cli, container.WithCmd("sh", "-c", tc.cmd))
  78. waitResC, errC := cli.ContainerWait(ctx, containerID, "")
  79. err := cli.ContainerStop(ctx, containerID, containertypes.StopOptions{})
  80. assert.NilError(t, err)
  81. select {
  82. case err := <-errC:
  83. assert.NilError(t, err)
  84. case waitRes := <-waitResC:
  85. assert.Check(t, is.Equal(tc.expectedCode, waitRes.StatusCode))
  86. case <-time.After(2 * time.Second):
  87. t.Fatal("timeout waiting for `docker wait`")
  88. }
  89. })
  90. }
  91. }
  92. func TestWaitConditions(t *testing.T) {
  93. ctx := setupTest(t)
  94. cli := request.NewAPIClient(t)
  95. testCases := []struct {
  96. doc string
  97. waitCond containertypes.WaitCondition
  98. runOpts []func(*container.TestContainerConfig)
  99. }{
  100. {
  101. doc: "default",
  102. },
  103. {
  104. doc: "not-running",
  105. waitCond: containertypes.WaitConditionNotRunning,
  106. },
  107. {
  108. doc: "next-exit",
  109. waitCond: containertypes.WaitConditionNextExit,
  110. },
  111. {
  112. doc: "removed",
  113. waitCond: containertypes.WaitConditionRemoved,
  114. runOpts: []func(*container.TestContainerConfig){container.WithAutoRemove},
  115. },
  116. }
  117. for _, tc := range testCases {
  118. tc := tc
  119. t.Run(tc.doc, func(t *testing.T) {
  120. t.Parallel()
  121. ctx := testutil.StartSpan(ctx, t)
  122. opts := append([]func(*container.TestContainerConfig){
  123. container.WithCmd("sh", "-c", "read -r; exit 99"),
  124. func(tcc *container.TestContainerConfig) {
  125. tcc.Config.AttachStdin = true
  126. tcc.Config.OpenStdin = true
  127. },
  128. }, tc.runOpts...)
  129. containerID := container.Create(ctx, t, cli, opts...)
  130. t.Logf("ContainerID = %v", containerID)
  131. streams, err := cli.ContainerAttach(ctx, containerID, containertypes.AttachOptions{Stream: true, Stdin: true})
  132. assert.NilError(t, err)
  133. defer streams.Close()
  134. assert.NilError(t, cli.ContainerStart(ctx, containerID, containertypes.StartOptions{}))
  135. waitResC, errC := cli.ContainerWait(ctx, containerID, tc.waitCond)
  136. select {
  137. case err := <-errC:
  138. t.Fatalf("ContainerWait() err = %v", err)
  139. case res := <-waitResC:
  140. t.Fatalf("ContainerWait() sent exit code (%v) before ContainerStart()", res)
  141. default:
  142. }
  143. info, _ := cli.ContainerInspect(ctx, containerID)
  144. assert.Equal(t, "running", info.State.Status)
  145. _, err = streams.Conn.Write([]byte("\n"))
  146. assert.NilError(t, err)
  147. select {
  148. case err := <-errC:
  149. assert.NilError(t, err)
  150. case waitRes := <-waitResC:
  151. assert.Check(t, is.Equal(int64(99), waitRes.StatusCode))
  152. case <-time.After(StopContainerWindowsPollTimeout):
  153. info, _ := cli.ContainerInspect(ctx, containerID)
  154. t.Fatalf("Timed out waiting for container exit code (status = %q)", info.State.Status)
  155. }
  156. })
  157. }
  158. }
  159. func TestWaitRestartedContainer(t *testing.T) {
  160. ctx := setupTest(t)
  161. cli := request.NewAPIClient(t)
  162. testCases := []struct {
  163. doc string
  164. waitCond containertypes.WaitCondition
  165. }{
  166. {
  167. doc: "default",
  168. },
  169. {
  170. doc: "not-running",
  171. waitCond: containertypes.WaitConditionNotRunning,
  172. },
  173. {
  174. doc: "next-exit",
  175. waitCond: containertypes.WaitConditionNextExit,
  176. },
  177. }
  178. // We can't catch the SIGTERM in the Windows based busybox image
  179. isWindowDaemon := testEnv.DaemonInfo.OSType == "windows"
  180. for _, tc := range testCases {
  181. tc := tc
  182. t.Run(tc.doc, func(t *testing.T) {
  183. t.Parallel()
  184. ctx := testutil.StartSpan(ctx, t)
  185. containerID := container.Run(ctx, t, cli,
  186. container.WithCmd("sh", "-c", "trap 'exit 5' SIGTERM; while true; do sleep 0.1; done"),
  187. )
  188. defer cli.ContainerRemove(ctx, containerID, containertypes.RemoveOptions{Force: true})
  189. // Container is running now, wait for exit
  190. waitResC, errC := cli.ContainerWait(ctx, containerID, tc.waitCond)
  191. timeout := 5
  192. // On Windows it will always timeout, because our process won't receive SIGTERM
  193. // Skip to force killing immediately
  194. if isWindowDaemon {
  195. timeout = 0
  196. }
  197. err := cli.ContainerRestart(ctx, containerID, containertypes.StopOptions{Timeout: &timeout, Signal: "SIGTERM"})
  198. assert.NilError(t, err)
  199. select {
  200. case err := <-errC:
  201. t.Fatalf("Unexpected error: %v", err)
  202. case <-time.After(time.Second * 3):
  203. t.Fatalf("Wait should end after restart")
  204. case waitRes := <-waitResC:
  205. expectedCode := int64(5)
  206. if !isWindowDaemon {
  207. assert.Check(t, is.Equal(expectedCode, waitRes.StatusCode))
  208. }
  209. }
  210. })
  211. }
  212. }