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  }