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