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