package containerd import ( "context" "io/ioutil" "os" "sync" "testing" "time" "github.com/docker/docker/libcontainerd" "github.com/gotestyourself/gotestyourself/assert" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) func TestLifeCycle(t *testing.T) { t.Parallel() mock := newMockClient() exec, cleanup := setupTest(t, mock, mock) defer cleanup() id := "test-create" mock.simulateStartError(true, id) err := exec.Create(id, specs.Spec{}, nil, nil) assert.Assert(t, err != nil) mock.simulateStartError(false, id) err = exec.Create(id, specs.Spec{}, nil, nil) assert.Assert(t, err) running, _ := exec.IsRunning(id) assert.Assert(t, running) // create with the same ID err = exec.Create(id, specs.Spec{}, nil, nil) assert.Assert(t, err != nil) mock.HandleExitEvent(id) // simulate a plugin that exits err = exec.Create(id, specs.Spec{}, nil, nil) assert.Assert(t, err) } func setupTest(t *testing.T, client Client, eh ExitHandler) (*Executor, func()) { rootDir, err := ioutil.TempDir("", "test-daemon") assert.Assert(t, err) assert.Assert(t, client != nil) assert.Assert(t, eh != nil) return &Executor{ rootDir: rootDir, client: client, exitHandler: eh, }, func() { assert.Assert(t, os.RemoveAll(rootDir)) } } type mockClient struct { mu sync.Mutex containers map[string]bool errorOnStart map[string]bool } func newMockClient() *mockClient { return &mockClient{ containers: make(map[string]bool), errorOnStart: make(map[string]bool), } } func (c *mockClient) Create(ctx context.Context, id string, _ *specs.Spec, _ interface{}) error { c.mu.Lock() defer c.mu.Unlock() if _, ok := c.containers[id]; ok { return errors.New("exists") } c.containers[id] = false return nil } func (c *mockClient) Restore(ctx context.Context, id string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) { return false, 0, nil } func (c *mockClient) Status(ctx context.Context, id string) (libcontainerd.Status, error) { c.mu.Lock() defer c.mu.Unlock() running, ok := c.containers[id] if !ok { return libcontainerd.StatusUnknown, errors.New("not found") } if running { return libcontainerd.StatusRunning, nil } return libcontainerd.StatusStopped, nil } func (c *mockClient) Delete(ctx context.Context, id string) error { c.mu.Lock() defer c.mu.Unlock() delete(c.containers, id) return nil } func (c *mockClient) DeleteTask(ctx context.Context, id string) (uint32, time.Time, error) { return 0, time.Time{}, nil } func (c *mockClient) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) { c.mu.Lock() defer c.mu.Unlock() if _, ok := c.containers[id]; !ok { return 0, errors.New("not found") } if c.errorOnStart[id] { return 0, errors.New("some startup error") } c.containers[id] = true return 1, nil } func (c *mockClient) SignalProcess(ctx context.Context, containerID, processID string, signal int) error { return nil } func (c *mockClient) simulateStartError(sim bool, id string) { c.mu.Lock() defer c.mu.Unlock() if sim { c.errorOnStart[id] = sim return } delete(c.errorOnStart, id) } func (c *mockClient) HandleExitEvent(id string) error { c.mu.Lock() defer c.mu.Unlock() delete(c.containers, id) return nil }