state_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package container // import "github.com/docker/docker/container"
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/docker/docker/api/types"
  7. libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
  8. )
  9. func TestIsValidHealthString(t *testing.T) {
  10. contexts := []struct {
  11. Health string
  12. Expected bool
  13. }{
  14. {types.Healthy, true},
  15. {types.Unhealthy, true},
  16. {types.Starting, true},
  17. {types.NoHealthcheck, true},
  18. {"fail", false},
  19. }
  20. for _, c := range contexts {
  21. v := IsValidHealthString(c.Health)
  22. if v != c.Expected {
  23. t.Fatalf("Expected %t, but got %t", c.Expected, v)
  24. }
  25. }
  26. }
  27. type mockTask struct {
  28. libcontainerdtypes.Task
  29. pid uint32
  30. }
  31. func (t *mockTask) Pid() uint32 { return t.pid }
  32. func TestStateRunStop(t *testing.T) {
  33. s := NewState()
  34. // Begin another wait with WaitConditionRemoved. It should complete
  35. // within 200 milliseconds.
  36. ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
  37. defer cancel()
  38. removalWait := s.Wait(ctx, WaitConditionRemoved)
  39. // Full lifecycle two times.
  40. for i := 1; i <= 2; i++ {
  41. // A wait with WaitConditionNotRunning should return
  42. // immediately since the state is now either "created" (on the
  43. // first iteration) or "exited" (on the second iteration). It
  44. // shouldn't take more than 50 milliseconds.
  45. ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
  46. defer cancel()
  47. // Expectx exit code to be i-1 since it should be the exit
  48. // code from the previous loop or 0 for the created state.
  49. if status := <-s.Wait(ctx, WaitConditionNotRunning); status.ExitCode() != i-1 {
  50. t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i-1, status.Err())
  51. }
  52. // A wait with WaitConditionNextExit should block until the
  53. // container has started and exited. It shouldn't take more
  54. // than 100 milliseconds.
  55. ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
  56. defer cancel()
  57. initialWait := s.Wait(ctx, WaitConditionNextExit)
  58. // Set the state to "Running".
  59. s.Lock()
  60. s.SetRunning(nil, &mockTask{pid: uint32(i)}, true)
  61. s.Unlock()
  62. // Assert desired state.
  63. if !s.IsRunning() {
  64. t.Fatal("State not running")
  65. }
  66. if s.Pid != i {
  67. t.Fatalf("Pid %v, expected %v", s.Pid, i)
  68. }
  69. if s.ExitCode() != 0 {
  70. t.Fatalf("ExitCode %v, expected 0", s.ExitCode())
  71. }
  72. // Now that it's running, a wait with WaitConditionNotRunning
  73. // should block until we stop the container. It shouldn't take
  74. // more than 100 milliseconds.
  75. ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
  76. defer cancel()
  77. exitWait := s.Wait(ctx, WaitConditionNotRunning)
  78. // Set the state to "Exited".
  79. s.Lock()
  80. s.SetStopped(&ExitStatus{ExitCode: i})
  81. s.Unlock()
  82. // Assert desired state.
  83. if s.IsRunning() {
  84. t.Fatal("State is running")
  85. }
  86. if s.ExitCode() != i {
  87. t.Fatalf("ExitCode %v, expected %v", s.ExitCode(), i)
  88. }
  89. if s.Pid != 0 {
  90. t.Fatalf("Pid %v, expected 0", s.Pid)
  91. }
  92. // Receive the initialWait result.
  93. if status := <-initialWait; status.ExitCode() != i {
  94. t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
  95. }
  96. // Receive the exitWait result.
  97. if status := <-exitWait; status.ExitCode() != i {
  98. t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
  99. }
  100. }
  101. // Set the state to dead and removed.
  102. s.Lock()
  103. s.Dead = true
  104. s.Unlock()
  105. s.SetRemoved()
  106. // Wait for removed status or timeout.
  107. if status := <-removalWait; status.ExitCode() != 2 {
  108. // Should have the final exit code from the loop.
  109. t.Fatalf("Removal wait exitCode %v, expected %v, err %q", status.ExitCode(), 2, status.Err())
  110. }
  111. }
  112. func TestStateTimeoutWait(t *testing.T) {
  113. s := NewState()
  114. s.Lock()
  115. s.SetRunning(nil, nil, true)
  116. s.Unlock()
  117. // Start a wait with a timeout.
  118. ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
  119. defer cancel()
  120. waitC := s.Wait(ctx, WaitConditionNotRunning)
  121. // It should timeout *before* this 200ms timer does.
  122. select {
  123. case <-time.After(200 * time.Millisecond):
  124. t.Fatal("Stop callback doesn't fire in 200 milliseconds")
  125. case status := <-waitC:
  126. t.Log("Stop callback fired")
  127. // Should be a timeout error.
  128. if status.Err() == nil {
  129. t.Fatal("expected timeout error, got nil")
  130. }
  131. if status.ExitCode() != -1 {
  132. t.Fatalf("expected exit code %v, got %v", -1, status.ExitCode())
  133. }
  134. }
  135. s.Lock()
  136. s.SetStopped(&ExitStatus{ExitCode: 0})
  137. s.Unlock()
  138. // Start another wait with a timeout. This one should return
  139. // immediately.
  140. ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
  141. defer cancel()
  142. waitC = s.Wait(ctx, WaitConditionNotRunning)
  143. select {
  144. case <-time.After(200 * time.Millisecond):
  145. t.Fatal("Stop callback doesn't fire in 200 milliseconds")
  146. case status := <-waitC:
  147. t.Log("Stop callback fired")
  148. if status.ExitCode() != 0 {
  149. t.Fatalf("expected exit code %v, got %v, err %q", 0, status.ExitCode(), status.Err())
  150. }
  151. }
  152. }
  153. // Related issue: #39352
  154. func TestCorrectStateWaitResultAfterRestart(t *testing.T) {
  155. s := NewState()
  156. s.Lock()
  157. s.SetRunning(nil, nil, true)
  158. s.Unlock()
  159. waitC := s.Wait(context.Background(), WaitConditionNotRunning)
  160. want := ExitStatus{ExitCode: 10, ExitedAt: time.Now()}
  161. s.Lock()
  162. s.SetRestarting(&want)
  163. s.Unlock()
  164. s.Lock()
  165. s.SetRunning(nil, nil, true)
  166. s.Unlock()
  167. got := <-waitC
  168. if got.exitCode != want.ExitCode {
  169. t.Fatalf("expected exit code %v, got %v", want.ExitCode, got.exitCode)
  170. }
  171. }
  172. func TestIsValidStateString(t *testing.T) {
  173. states := []struct {
  174. state string
  175. expected bool
  176. }{
  177. {"paused", true},
  178. {"restarting", true},
  179. {"running", true},
  180. {"dead", true},
  181. {"start", false},
  182. {"created", true},
  183. {"exited", true},
  184. {"removing", true},
  185. {"stop", false},
  186. }
  187. for _, s := range states {
  188. v := IsValidStateString(s.state)
  189. if v != s.expected {
  190. t.Fatalf("Expected %t, but got %t", s.expected, v)
  191. }
  192. }
  193. }