container/state_test.go
6bb0d181
 package container
47065b90
 
 import (
cfdf84d5
 	"context"
47065b90
 	"testing"
 	"time"
1a149a0e
 
 	"github.com/docker/docker/api/types"
47065b90
 )
 
1a149a0e
 func TestIsValidHealthString(t *testing.T) {
 	contexts := []struct {
 		Health   string
 		Expected bool
 	}{
 		{types.Healthy, true},
 		{types.Unhealthy, true},
 		{types.Starting, true},
 		{types.NoHealthcheck, true},
 		{"fail", false},
 	}
 
 	for _, c := range contexts {
 		v := IsValidHealthString(c.Health)
 		if v != c.Expected {
 			t.Fatalf("Expected %t, but got %t", c.Expected, v)
 		}
 	}
 }
 
47065b90
 func TestStateRunStop(t *testing.T) {
 	s := NewState()
cfdf84d5
 
49211715
 	// Begin another wait with WaitConditionRemoved. It should complete
cfdf84d5
 	// within 200 milliseconds.
49211715
 	ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
cfdf84d5
 	defer cancel()
49211715
 	removalWait := s.Wait(ctx, WaitConditionRemoved)
cfdf84d5
 
 	// Full lifecycle two times.
 	for i := 1; i <= 2; i++ {
49211715
 		// A wait with WaitConditionNotRunning should return
 		// immediately since the state is now either "created" (on the
 		// first iteration) or "exited" (on the second iteration). It
 		// shouldn't take more than 50 milliseconds.
 		ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
 		defer cancel()
 		// Expectx exit code to be i-1 since it should be the exit
 		// code from the previous loop or 0 for the created state.
 		if status := <-s.Wait(ctx, WaitConditionNotRunning); status.ExitCode() != i-1 {
 			t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i-1, status.Err())
 		}
 
 		// A wait with WaitConditionNextExit should block until the
 		// container has started and exited. It shouldn't take more
 		// than 100 milliseconds.
 		ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
 		defer cancel()
 		initialWait := s.Wait(ctx, WaitConditionNextExit)
 
cfdf84d5
 		// Set the state to "Running".
eff253df
 		s.Lock()
cfdf84d5
 		s.SetRunning(i, true)
eff253df
 		s.Unlock()
abd72d40
 
cfdf84d5
 		// Assert desired state.
47065b90
 		if !s.IsRunning() {
 			t.Fatal("State not running")
 		}
cfdf84d5
 		if s.Pid != i {
 			t.Fatalf("Pid %v, expected %v", s.Pid, i)
47065b90
 		}
dcfe9927
 		if s.ExitCode() != 0 {
 			t.Fatalf("ExitCode %v, expected 0", s.ExitCode())
47065b90
 		}
 
49211715
 		// Now that it's running, a wait with WaitConditionNotRunning
 		// should block until we stop the container. It shouldn't take
 		// more than 100 milliseconds.
 		ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
cfdf84d5
 		defer cancel()
49211715
 		exitWait := s.Wait(ctx, WaitConditionNotRunning)
cfdf84d5
 
 		// Set the state to "Exited".
a28c389d
 		s.Lock()
 		s.SetStopped(&ExitStatus{ExitCode: i})
 		s.Unlock()
cfdf84d5
 
 		// Assert desired state.
47065b90
 		if s.IsRunning() {
 			t.Fatal("State is running")
 		}
dcfe9927
 		if s.ExitCode() != i {
 			t.Fatalf("ExitCode %v, expected %v", s.ExitCode(), i)
47065b90
 		}
 		if s.Pid != 0 {
 			t.Fatalf("Pid %v, expected 0", s.Pid)
 		}
cfdf84d5
 
49211715
 		// Receive the initialWait result.
 		if status := <-initialWait; status.ExitCode() != i {
cfdf84d5
 			t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
47065b90
 		}
cfdf84d5
 
49211715
 		// Receive the exitWait result.
 		if status := <-exitWait; status.ExitCode() != i {
cfdf84d5
 			t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
47065b90
 		}
 	}
cfdf84d5
 
 	// Set the state to dead and removed.
 	s.SetDead()
 	s.SetRemoved()
 
 	// Wait for removed status or timeout.
49211715
 	if status := <-removalWait; status.ExitCode() != 2 {
cfdf84d5
 		// Should have the final exit code from the loop.
 		t.Fatalf("Removal wait exitCode %v, expected %v, err %q", status.ExitCode(), 2, status.Err())
 	}
47065b90
 }
 
 func TestStateTimeoutWait(t *testing.T) {
 	s := NewState()
cfdf84d5
 
49211715
 	s.Lock()
 	s.SetRunning(0, true)
 	s.Unlock()
 
cfdf84d5
 	// Start a wait with a timeout.
 	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
 	defer cancel()
49211715
 	waitC := s.Wait(ctx, WaitConditionNotRunning)
cfdf84d5
 
 	// It should timeout *before* this 200ms timer does.
47065b90
 	select {
 	case <-time.After(200 * time.Millisecond):
51f927d4
 		t.Fatal("Stop callback doesn't fire in 200 milliseconds")
cfdf84d5
 	case status := <-waitC:
a0191a23
 		t.Log("Stop callback fired")
cfdf84d5
 		// Should be a timeout error.
 		if status.Err() == nil {
 			t.Fatal("expected timeout error, got nil")
 		}
 		if status.ExitCode() != -1 {
 			t.Fatalf("expected exit code %v, got %v", -1, status.ExitCode())
 		}
47065b90
 	}
abd72d40
 
a28c389d
 	s.Lock()
cfdf84d5
 	s.SetStopped(&ExitStatus{ExitCode: 0})
a28c389d
 	s.Unlock()
abd72d40
 
cfdf84d5
 	// Start another wait with a timeout. This one should return
 	// immediately.
 	ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
 	defer cancel()
49211715
 	waitC = s.Wait(ctx, WaitConditionNotRunning)
cfdf84d5
 
47065b90
 	select {
 	case <-time.After(200 * time.Millisecond):
92ee5a5d
 		t.Fatal("Stop callback doesn't fire in 200 milliseconds")
cfdf84d5
 	case status := <-waitC:
a0191a23
 		t.Log("Stop callback fired")
cfdf84d5
 		if status.ExitCode() != 0 {
 			t.Fatalf("expected exit code %v, got %v, err %q", 0, status.ExitCode(), status.Err())
 		}
47065b90
 	}
 }
675ac374
 
 func TestIsValidStateString(t *testing.T) {
 	states := []struct {
 		state    string
 		expected bool
 	}{
 		{"paused", true},
 		{"restarting", true},
 		{"running", true},
 		{"dead", true},
 		{"start", false},
 		{"created", true},
 		{"exited", true},
 		{"removing", true},
 		{"stop", false},
 	}
 
 	for _, s := range states {
 		v := IsValidStateString(s.state)
 		if v != s.expected {
 			t.Fatalf("Expected %t, but got %t", s.expected, v)
 		}
 	}
 }