package containerd import ( "bytes" "fmt" "io" "io/ioutil" "os" "runtime" "strings" "sync" "syscall" "testing" "time" // Register the typeurl _ "github.com/containerd/containerd/runtime" "github.com/containerd/containerd/errdefs" ) func empty() IOCreation { return NullIO } func TestContainerList(t *testing.T) { client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() ctx, cancel := testContext() defer cancel() containers, err := client.Containers(ctx) if err != nil { t.Errorf("container list returned error %v", err) return } if len(containers) != 0 { t.Errorf("expected 0 containers but received %d", len(containers)) } } func TestNewContainer(t *testing.T) { t.Parallel() id := t.Name() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() spec, err := generateSpec() if err != nil { t.Error(err) return } ctx, cancel := testContext() defer cancel() container, err := client.NewContainer(ctx, id, WithSpec(spec)) if err != nil { t.Error(err) return } defer container.Delete(ctx) if container.ID() != id { t.Errorf("expected container id %q but received %q", id, container.ID()) } if _, err = container.Spec(); err != nil { t.Error(err) return } if err := container.Delete(ctx); err != nil { t.Error(err) return } } func TestContainerStart(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withExitStatus(7)) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, Stdio) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if pid := task.Pid(); pid <= 0 { t.Errorf("invalid task pid %d", pid) } if err := task.Start(ctx); err != nil { t.Error(err) task.Delete(ctx) return } status := <-statusC code, _, err := status.Result() if err != nil { t.Error(err) return } if code != 7 { t.Errorf("expected status 7 from wait but received %d", code) } deleteStatus, err := task.Delete(ctx) if err != nil { t.Error(err) return } if deleteStatus != 7 { t.Errorf("expected status 7 from delete but received %d", deleteStatus) } } func TestContainerOutput(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() expected = "kingkoye" ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("echo", expected)) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) stdout := bytes.NewBuffer(nil) task, err := container.NewTask(ctx, NewIO(bytes.NewBuffer(nil), stdout, bytes.NewBuffer(nil))) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } status := <-statusC code, _, _ := status.Result() if code != 0 { t.Errorf("expected status 0 but received %d", code) } if _, err := task.Delete(ctx); err != nil { t.Error(err) return } actual := stdout.String() // echo adds a new line expected = expected + newLine if actual != expected { t.Errorf("expected output %q but received %q", expected, actual) } } func TestContainerExec(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, empty()) if err != nil { t.Error(err) return } defer task.Delete(ctx) finishedC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } // start an exec process without running the original container process info processSpec := spec.Process withExecExitStatus(processSpec, 6) execID := t.Name() + "_exec" process, err := task.Exec(ctx, execID, processSpec, empty()) if err != nil { t.Error(err) return } processStatusC, err := process.Wait(ctx) if err != nil { t.Error(err) return } if err := process.Start(ctx); err != nil { t.Error(err) return } // wait for the exec to return status := <-processStatusC code, _, err := status.Result() if err != nil { t.Error(err) return } if code != 6 { t.Errorf("expected exec exit code 6 but received %d", code) } deleteStatus, err := process.Delete(ctx) if err != nil { t.Error(err) return } if deleteStatus != 6 { t.Errorf("expected delete exit code e6 but received %d", deleteStatus) } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) } <-finishedC } func TestContainerPids(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, empty()) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } pid := task.Pid() if pid <= 0 { t.Errorf("invalid task pid %d", pid) } processes, err := task.Pids(ctx) if err != nil { t.Error(err) return } if l := len(processes); l != 1 { t.Errorf("expected 1 process but received %d", l) } if len(processes) > 0 { actual := processes[0] if pid != actual { t.Errorf("expected pid %d but received %d", pid, actual) } } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) } <-statusC } func TestContainerCloseIO(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withCat()) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) const expected = "hello" + newLine stdout := bytes.NewBuffer(nil) r, w, err := os.Pipe() if err != nil { t.Error(err) return } task, err := container.NewTask(ctx, NewIO(r, stdout, ioutil.Discard)) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } if _, err := fmt.Fprint(w, expected); err != nil { t.Error(err) } w.Close() if err := task.CloseIO(ctx, WithStdinCloser); err != nil { t.Error(err) } <-statusC if _, err := task.Delete(ctx); err != nil { t.Error(err) } output := stdout.String() if runtime.GOOS == "windows" { // On windows we use more and it always adds an extra newline // remove it here output = strings.TrimSuffix(output, newLine) } if output != expected { t.Errorf("expected output %q but received %q", expected, output) } } func TestContainerAttach(t *testing.T) { t.Parallel() if runtime.GOOS == "windows" { // On windows, closing the write side of the pipe closes the read // side, sending an EOF to it and preventing reopening it. // Hence this test will always fails on windows t.Skip("invalid logic on windows") } client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withCat()) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) expected := "hello" + newLine stdout := bytes.NewBuffer(nil) r, w, err := os.Pipe() if err != nil { t.Error(err) return } or, ow, err := os.Pipe() if err != nil { t.Error(err) return } wg := &sync.WaitGroup{} wg.Add(1) go func() { io.Copy(stdout, or) wg.Done() }() task, err := container.NewTask(ctx, NewIO(r, ow, ioutil.Discard)) if err != nil { t.Error(err) return } defer task.Delete(ctx) originalIO := task.IO() statusC, err := task.Wait(ctx) if err != nil { t.Error(err) } if err := task.Start(ctx); err != nil { t.Error(err) return } if _, err := fmt.Fprint(w, expected); err != nil { t.Error(err) } w.Close() // load the container and re-load the task if container, err = client.LoadContainer(ctx, id); err != nil { t.Error(err) return } // create new IO for the loaded task if r, w, err = os.Pipe(); err != nil { t.Error(err) return } if task, err = container.Task(ctx, WithAttach(r, ow, ioutil.Discard)); err != nil { t.Error(err) return } if _, err := fmt.Fprint(w, expected); err != nil { t.Error(err) } w.Close() if err := task.CloseIO(ctx, WithStdinCloser); err != nil { t.Error(err) } status := <-statusC _, _, err = status.Result() if err != nil { t.Error(err) return } originalIO.Close() if _, err := task.Delete(ctx); err != nil { t.Error(err) } ow.Close() wg.Wait() output := stdout.String() // we wrote the same thing after attach expected = expected + expected if output != expected { t.Errorf("expected output %q but received %q", expected, output) } } func TestDeleteRunningContainer(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, empty()) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } err = container.Delete(ctx, WithSnapshotCleanup) if err == nil { t.Error("delete did not error with running task") } if !errdefs.IsFailedPrecondition(err) { t.Errorf("expected error %q but received %q", errdefs.ErrFailedPrecondition, err) } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) return } <-statusC } func TestContainerKill(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withCat()) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx) task, err := container.NewTask(ctx, Stdio) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) return } <-statusC err = task.Kill(ctx, syscall.SIGTERM) if err == nil { t.Error("second call to kill should return an error") return } if !errdefs.IsNotFound(err) { t.Errorf("expected error %q but received %q", errdefs.ErrNotFound, err) } } func TestContainerNoBinaryExists(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), WithProcessArgs("nothing")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, Stdio) switch runtime.GOOS { case "windows": if err != nil { t.Fatalf("failed to create task %v", err) } defer task.Delete(ctx, WithProcessKill) if err := task.Start(ctx); err == nil { t.Error("task.Start() should return an error when binary does not exist") } default: if err == nil { t.Error("NewTask should return an error when binary does not exist") task.Delete(ctx) } } } func TestContainerExecNoBinaryExists(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, empty()) if err != nil { t.Error(err) return } defer task.Delete(ctx) finishedC, err := task.Wait(ctx) if err != nil { t.Error(err) } if err := task.Start(ctx); err != nil { t.Error(err) return } // start an exec process without running the original container process processSpec := spec.Process processSpec.Args = []string{ "none", } execID := t.Name() + "_exec" process, err := task.Exec(ctx, execID, processSpec, empty()) if err != nil { t.Error(err) return } defer process.Delete(ctx) if err := process.Start(ctx); err == nil { t.Error("Process.Start should fail when process does not exist") } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) } <-finishedC } func TestUserNamespaces(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec( withImageConfig(ctx, image), withExitStatus(7), withUserNamespace(0, 1000, 10000), ) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withRemappedSnapshot(id, image, 1000, 1000), ) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, Stdio) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if pid := task.Pid(); pid <= 0 { t.Errorf("invalid task pid %d", pid) } if err := task.Start(ctx); err != nil { t.Error(err) task.Delete(ctx) return } status := <-statusC code, _, err := status.Result() if err != nil { t.Error(err) return } if code != 7 { t.Errorf("expected status 7 from wait but received %d", code) } deleteStatus, err := task.Delete(ctx) if err != nil { t.Error(err) return } if deleteStatus != 7 { t.Errorf("expected status 7 from delete but received %d", deleteStatus) } } func TestWaitStoppedTask(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withExitStatus(7)) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, Stdio) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if pid := task.Pid(); pid <= 0 { t.Errorf("invalid task pid %d", pid) } if err := task.Start(ctx); err != nil { t.Error(err) task.Delete(ctx) return } // wait for the task to stop then call wait again <-statusC statusC, err = task.Wait(ctx) if err != nil { t.Error(err) return } status := <-statusC code, _, err := status.Result() if err != nil { t.Error(err) return } if code != 7 { t.Errorf("exit status from stopped task should be 7 but received %d", code) } } func TestWaitStoppedProcess(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, empty()) if err != nil { t.Error(err) return } defer task.Delete(ctx) finishedC, err := task.Wait(ctx) if err != nil { t.Error(err) } if err := task.Start(ctx); err != nil { t.Error(err) return } // start an exec process without running the original container process info processSpec := spec.Process withExecExitStatus(processSpec, 6) execID := t.Name() + "_exec" process, err := task.Exec(ctx, execID, processSpec, empty()) if err != nil { t.Error(err) return } defer process.Delete(ctx) statusC, err := process.Wait(ctx) if err != nil { t.Error(err) return } if err := process.Start(ctx); err != nil { t.Error(err) return } // wait for the exec to return <-statusC // try to wait on the process after it has stopped statusC, err = process.Wait(ctx) if err != nil { t.Error(err) return } status := <-statusC code, _, err := status.Result() if err != nil { t.Error(err) return } if code != 6 { t.Errorf("exit status from stopped process should be 6 but received %d", code) } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) } <-finishedC } func TestTaskForceDelete(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "30")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, Stdio) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } if _, err := task.Delete(ctx); err == nil { t.Error("task.Delete of a running task should create an error") } if _, err := task.Delete(ctx, WithProcessKill); err != nil { t.Error(err) return } } func TestProcessForceDelete(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "30")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, Stdio) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } // task must be started on windows if err := task.Start(ctx); err != nil { t.Error(err) return } processSpec := spec.Process withExecArgs(processSpec, "sleep", "20") execID := t.Name() + "_exec" process, err := task.Exec(ctx, execID, processSpec, empty()) if err != nil { t.Error(err) return } if err := process.Start(ctx); err != nil { t.Error(err) return } if _, err := process.Delete(ctx); err == nil { t.Error("process.Delete should return an error when process is running") } if _, err := process.Delete(ctx, WithProcessKill); err != nil { t.Error(err) } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) return } <-statusC } func TestContainerHostname(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() expected = "myhostname" ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec( withImageConfig(ctx, image), withProcessArgs("hostname"), WithHostname(expected), ) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) stdout := bytes.NewBuffer(nil) task, err := container.NewTask(ctx, NewIO(bytes.NewBuffer(nil), stdout, bytes.NewBuffer(nil))) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) return } if err := task.Start(ctx); err != nil { t.Error(err) return } status := <-statusC code, _, err := status.Result() if err != nil { t.Error(err) return } if code != 0 { t.Errorf("expected status 0 but received %d", code) } if _, err := task.Delete(ctx); err != nil { t.Error(err) return } cutset := "\n" if runtime.GOOS == "windows" { cutset = "\r\n" } actual := strings.TrimSuffix(stdout.String(), cutset) if actual != expected { t.Errorf("expected output %q but received %q", expected, actual) } } func TestContainerExitedAtSet(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withTrue()) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, empty()) if err != nil { t.Error(err) return } defer task.Delete(ctx) statusC, err := task.Wait(ctx) if err != nil { t.Error(err) } startTime := time.Now() if err := task.Start(ctx); err != nil { t.Error(err) return } status := <-statusC code, _, _ := status.Result() if code != 0 { t.Errorf("expected status 0 but received %d", code) } if s, err := task.Status(ctx); err != nil { t.Errorf("failed to retrieve status: %v", err) } else if s.ExitTime.After(startTime) == false { t.Errorf("exit time is not after start time: %v <= %v", startTime, s.ExitTime) } if _, err := task.Delete(ctx); err != nil { t.Error(err) return } } func TestDeleteContainerExecCreated(t *testing.T) { t.Parallel() client, err := newClient(t, address) if err != nil { t.Fatal(err) } defer client.Close() var ( image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() if runtime.GOOS != "windows" { image, err = client.GetImage(ctx, testImage) if err != nil { t.Error(err) return } } spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithSnapshotCleanup) task, err := container.NewTask(ctx, empty()) if err != nil { t.Error(err) return } defer task.Delete(ctx) finished, err := task.Wait(ctx) if err != nil { t.Error(err) } if err := task.Start(ctx); err != nil { t.Error(err) return } // start an exec process without running the original container process info processSpec := spec.Process withExecExitStatus(processSpec, 6) execID := t.Name() + "_exec" process, err := task.Exec(ctx, execID, processSpec, empty()) if err != nil { t.Error(err) return } deleteStatus, err := process.Delete(ctx) if err != nil { t.Error(err) return } if deleteStatus != 0 { t.Errorf("expected delete exit code 0 but received %d", deleteStatus) } if err := task.Kill(ctx, syscall.SIGKILL); err != nil { t.Error(err) } <-finished }