Repository: pingcap/chaos-mesh
Pull request #337 (2020-03-12 17:13:18)

Unit Tests for podchaos
UCP #264 

### What problem does this PR solve? <!--add and issue link with summary if exists-->
#264 

### What is changed and how does it work?

### Check List <!--REMOVE the items that are not applicable-->

Tests <!-- At least one of them must be included. -->

 - Unit test

Code changes

 - Has Go code change

Side effects

Related changes

### Does this PR introduce a user-facing change?:
<!--
If no, just leave the release note block below as is.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note
NONE
```


sre-bot: Thanks for your contribution. If your PR get merged, you will be rewarded 50 points.
codecov-io: # [Codecov](https://codecov.io/gh/pingcap/chaos-mesh/pull/337?src=pr&el=h1) Report
> Merging [#337](https://codecov.io/gh/pingcap/chaos-mesh/pull/337?src=pr&el=desc) into [master](https://codecov.io/gh/pingcap/chaos-mesh/commit/0724ec999d470e0ecc5c7edb641a66194ee5eb16&el=desc) will **not change** coverage by `%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/pingcap/chaos-mesh/pull/337/graphs/tree.svg?width=650&height=150&src=pr&token=a3XIzuy5Dk)](https://codecov.io/gh/pingcap/chaos-mesh/pull/337?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #337   +/-   ##
=======================================
  Coverage   59.02%   59.02%           
=======================================
  Files          55       55           
  Lines        3165     3165           
=======================================
  Hits         1868     1868           
  Misses       1153     1153           
  Partials      144      144           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/pingcap/chaos-mesh/pull/337?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pingcap/chaos-mesh/pull/337?src=pr&el=footer). Last update [0724ec9...3d9b46f](https://codecov.io/gh/pingcap/chaos-mesh/pull/337?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

oraluben: @fewdan PTAL
zhouqiang-cl: @cwen0 PTAL too
cwen0: /merge
sre-bot: /run-all-tests

Patch:
diff --git a/controllers/iochaos/fs/types.go b/controllers/iochaos/fs/types.go
index 32005381..4286eb6d 100644
--- a/controllers/iochaos/fs/types.go
+++ b/controllers/iochaos/fs/types.go
@@ -86,7 +86,7 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, chaos reconcil
 
 	pods, err := utils.SelectAndFilterPods(ctx, r.Client, &iochaos.Spec)
 	if err != nil {
-		r.Log.Error(err, "failed to select and generate pods")
+		r.Log.Error(err, "failed to select and filter pods")
 		return err
 	}
 
diff --git a/controllers/kernelchaos/types.go b/controllers/kernelchaos/types.go
index 85b445db..47008f0b 100644
--- a/controllers/kernelchaos/types.go
+++ b/controllers/kernelchaos/types.go
@@ -97,7 +97,7 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, chaos reconcil
 	pods, err := utils.SelectAndFilterPods(ctx, r.Client, &kernelchaos.Spec)
 
 	if err != nil {
-		r.Log.Error(err, "failed to select and generate pods")
+		r.Log.Error(err, "failed to select and filter pods")
 		return err
 	}
 
diff --git a/controllers/networkchaos/netem/types.go b/controllers/networkchaos/netem/types.go
index 57fc9e76..e4e9968e 100644
--- a/controllers/networkchaos/netem/types.go
+++ b/controllers/networkchaos/netem/types.go
@@ -98,7 +98,7 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, chaos reconcil
 	pods, err := utils.SelectAndFilterPods(ctx, r.Client, &networkchaos.Spec)
 
 	if err != nil {
-		r.Log.Error(err, "failed to select and generate pods")
+		r.Log.Error(err, "failed to select and filter pods")
 		return err
 	}
 
diff --git a/controllers/networkchaos/partition/types.go b/controllers/networkchaos/partition/types.go
index 4bb5af11..4540ff42 100644
--- a/controllers/networkchaos/partition/types.go
+++ b/controllers/networkchaos/partition/types.go
@@ -98,14 +98,14 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, chaos reconcil
 	sources, err := utils.SelectAndFilterPods(ctx, r.Client, &networkchaos.Spec)
 
 	if err != nil {
-		r.Log.Error(err, "failed to select and generate pods")
+		r.Log.Error(err, "failed to select and filter pods")
 		return err
 	}
 
 	targets, err := utils.SelectAndFilterPods(ctx, r.Client, &networkchaos.Spec.Target)
 
 	if err != nil {
-		r.Log.Error(err, "failed to select and generate pods")
+		r.Log.Error(err, "failed to select and filter pods")
 		return err
 	}
 
diff --git a/controllers/podchaos/containerkill/types.go b/controllers/podchaos/containerkill/types.go
index 0ef25939..36b4e609 100644
--- a/controllers/podchaos/containerkill/types.go
+++ b/controllers/podchaos/containerkill/types.go
@@ -15,6 +15,7 @@ package containerkill
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"time"
@@ -65,11 +66,10 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, obj reconciler
 	var err error
 	now := time.Now()
 
-	r.Log.Info("Reconciling container kill")
-
-	var podchaos v1alpha1.PodChaos
-	if err = r.Get(ctx, req.NamespacedName, &podchaos); err != nil {
-		r.Log.Error(err, "unable to get podchaos")
+	podchaos, ok := obj.(*v1alpha1.PodChaos)
+	if !ok {
+		err = errors.New("chaos is not PodChaos")
+		r.Log.Error(err, "chaos is not PodChaos", "chaos", obj)
 		return err
 	}
 
@@ -78,26 +78,15 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, obj reconciler
 		return fmt.Errorf("podchaos[%s/%s] the name of container is empty", podchaos.Namespace, podchaos.Name)
 	}
 
-	pods, err := utils.SelectPods(ctx, r.Client, podchaos.Spec.Selector)
-	if err != nil {
-		r.Log.Error(err, "fail to get selected pods")
-		return err
-	}
-
-	if len(pods) == 0 {
-		r.Log.Error(nil, "no pod is selected", "name", req.Name, "namespace", req.Namespace)
-		return fmt.Errorf("podchaos[%s/%s] no pod is selected", podchaos.Namespace, podchaos.Name)
-	}
-
-	filteredPod, err := utils.FilterPodsByMode(pods, podchaos.Spec.Mode, podchaos.Spec.Value)
+	pods, err := utils.SelectAndFilterPods(ctx, r.Client, &podchaos.Spec)
 	if err != nil {
-		r.Log.Error(err, "fail to generate pods")
+		r.Log.Error(err, "fail to select and filter pods")
 		return err
 	}
 
 	g := errgroup.Group{}
-	for podIndex := range filteredPod {
-		pod := &filteredPod[podIndex]
+	for podIndex := range pods {
+		pod := &pods[podIndex]
 		haveContainer := false
 
 		for containerIndex := range pod.Status.ContainerStatuses {
@@ -106,10 +95,13 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, obj reconciler
 
 			if containerName == podchaos.Spec.ContainerName {
 				haveContainer = true
-				err = r.KillContainer(ctx, pod, containerID)
-				if err != nil {
-					r.Log.Error(err, "failed to kill container")
-				}
+				g.Go(func() error {
+					err = r.KillContainer(ctx, pod, containerID)
+					if err != nil {
+						r.Log.Error(err, "failed to kill container")
+					}
+					return err
+				})
 			}
 		}
 
@@ -121,7 +113,7 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, obj reconciler
 	if err := g.Wait(); err != nil {
 		return err
 	}
-	if err = r.updatePodchaos(ctx, podchaos, pods, now); err != nil {
+	if err = r.updatePodchaos(ctx, *podchaos, pods, now); err != nil {
 		return err
 	}
 	r.Event(obj, v1.EventTypeNormal, utils.EventChaosInjected, "")
diff --git a/controllers/podchaos/podfailure/types.go b/controllers/podchaos/podfailure/types.go
index 02daad9b..3b0ef54f 100644
--- a/controllers/podchaos/podfailure/types.go
+++ b/controllers/podchaos/podfailure/types.go
@@ -91,7 +91,7 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, chaos reconcil
 
 	pods, err := utils.SelectAndFilterPods(ctx, r.Client, &podchaos.Spec)
 	if err != nil {
-		r.Log.Error(err, "failed to select and generate pods")
+		r.Log.Error(err, "failed to select and filter pods")
 		return err
 	}
 	err = r.failAllPods(ctx, pods, podchaos)
diff --git a/controllers/podchaos/podkill/types.go b/controllers/podchaos/podkill/types.go
index 84078b8b..a4be69d5 100644
--- a/controllers/podchaos/podkill/types.go
+++ b/controllers/podchaos/podkill/types.go
@@ -64,24 +64,15 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, chaos reconcil
 		r.Log.Error(err, "chaos is not PodChaos", "chaos", chaos)
 		return err
 	}
-	pods, err := utils.SelectPods(ctx, r.Client, podchaos.Spec.Selector)
+	pods, err := utils.SelectAndFilterPods(ctx, r.Client, &podchaos.Spec)
 	if err != nil {
-		r.Log.Error(err, "fail to get selected pods")
-		return err
-	}
-	if len(pods) == 0 {
-		r.Log.Error(nil, "no pod is selected", "name", req.Name, "namespace", req.Namespace)
-		return err
-	}
-	filteredPod, err := utils.FilterPodsByMode(pods, podchaos.Spec.Mode, podchaos.Spec.Value)
-	if err != nil {
-		r.Log.Error(err, "fail to generate pods")
+		r.Log.Error(err, "fail to select and generate pods")
 		return err
 	}
 
 	g := errgroup.Group{}
-	for index := range filteredPod {
-		pod := &filteredPod[index]
+	for index := range pods {
+		pod := &pods[index]
 		g.Go(func() error {
 			r.Log.Info("Deleting", "namespace", pod.Namespace, "name", pod.Name)
 
diff --git a/controllers/timechaos/types.go b/controllers/timechaos/types.go
index d4e6aa25..0ef1c118 100644
--- a/controllers/timechaos/types.go
+++ b/controllers/timechaos/types.go
@@ -90,7 +90,7 @@ func (r *Reconciler) Apply(ctx context.Context, req ctrl.Request, chaos reconcil
 	pods, err := utils.SelectAndFilterPods(ctx, r.Client, &timechaos.Spec)
 
 	if err != nil {
-		r.Log.Error(err, "failed to select and generate pods")
+		r.Log.Error(err, "failed to select and filter pods")
 		return err
 	}
 
diff --git a/pkg/utils/selector.go b/pkg/utils/selector.go
index 0375fcfc..332ad00d 100644
--- a/pkg/utils/selector.go
+++ b/pkg/utils/selector.go
@@ -43,10 +43,10 @@ type SelectSpec interface {
 
 // SelectAndFilterPods returns the list of pods that filtered by selector and PodMode
 func SelectAndFilterPods(ctx context.Context, c client.Client, spec SelectSpec) ([]v1.Pod, error) {
-	if pods := mock.On("MockSelectAndGeneratePods"); pods != nil {
+	if pods := mock.On("MockSelectAndFilterPods"); pods != nil {
 		return pods.(func() []v1.Pod)(), nil
 	}
-	if err := mock.On("MockSelectedAndGeneratePodsError"); err != nil {
+	if err := mock.On("MockSelectedAndFilterPodsError"); err != nil {
 		return nil, err.(error)
 	}
 
@@ -54,7 +54,7 @@ func SelectAndFilterPods(ctx context.Context, c client.Client, spec SelectSpec)
 	mode := spec.GetMode()
 	value := spec.GetValue()
 
-	pods, err := SelectPods(ctx, c, selector)
+	pods, err := selectPods(ctx, c, selector)
 	if err != nil {
 		return nil, err
 	}
@@ -64,7 +64,7 @@ func SelectAndFilterPods(ctx context.Context, c client.Client, spec SelectSpec)
 		return nil, err
 	}
 
-	filteredPod, err := FilterPodsByMode(pods, mode, value)
+	filteredPod, err := filterPodsByMode(pods, mode, value)
 	if err != nil {
 		return nil, err
 	}
@@ -72,10 +72,10 @@ func SelectAndFilterPods(ctx context.Context, c client.Client, spec SelectSpec)
 	return filteredPod, nil
 }
 
-// SelectPods returns the list of pods that are available for pod chaos action.
+// selectPods returns the list of pods that are available for pod chaos action.
 // It returns all pods that match the configured label, annotation and namespace selectors.
 // If pods are specifically specified by `selector.Pods`, it just returns the selector.Pods.
-func SelectPods(ctx context.Context, c client.Client, selector v1alpha1.SelectorSpec) ([]v1.Pod, error) {
+func selectPods(ctx context.Context, c client.Client, selector v1alpha1.SelectorSpec) ([]v1.Pod, error) {
 	var pods []v1.Pod
 
 	// pods are specifically specified
@@ -216,8 +216,8 @@ func CheckPodMeetSelector(pod v1.Pod, selector v1alpha1.SelectorSpec) (bool, err
 	return false, nil
 }
 
-// FilterPodsByMode filters pods by mode from pod list
-func FilterPodsByMode(pods []v1.Pod, mode v1alpha1.PodMode, value string) ([]v1.Pod, error) {
+// filterPodsByMode filters pods by mode from pod list
+func filterPodsByMode(pods []v1.Pod, mode v1alpha1.PodMode, value string) ([]v1.Pod, error) {
 	if len(pods) == 0 {
 		return nil, errors.New("cannot generate pods from empty list")
 	}


Test patch:
diff --git a/controllers/podchaos/containerkill/containerkill_suite_test.go b/controllers/podchaos/containerkill/containerkill_suite_test.go
new file mode 100644
index 00000000..038c5f91
--- /dev/null
+++ b/controllers/podchaos/containerkill/containerkill_suite_test.go
@@ -0,0 +1,97 @@
+package containerkill_test
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/tools/record"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+	"github.com/pingcap/chaos-mesh/api/v1alpha1"
+	controllerkill "github.com/pingcap/chaos-mesh/controllers/podchaos/containerkill"
+	. "github.com/pingcap/chaos-mesh/controllers/test"
+	"github.com/pingcap/chaos-mesh/pkg/mock"
+)
+
+func TestContainerKill(t *testing.T) {
+	RegisterFailHandler(Fail)
+
+	RunSpecsWithDefaultAndCustomReporters(t,
+		"ContainerKill Suite",
+		[]Reporter{envtest.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func(done Done) {
+	logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
+
+	Expect(v1.AddToScheme(scheme.Scheme)).To(Succeed())
+	Expect(v1alpha1.AddToScheme(scheme.Scheme)).To(Succeed())
+
+	close(done)
+}, 60)
+
+var _ = AfterSuite(func() {
+})
+
+var _ = Describe("PodChaos", func() {
+	Context("ContainerKill", func() {
+		objs, _ := GenerateNPods("p", 1, v1.PodRunning, metav1.NamespaceDefault, nil, nil, v1.ContainerStatus{
+			ContainerID: "fake-container-id",
+			Name:        "container-name",
+		})
+
+		podChaos := v1alpha1.PodChaos{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "PodChaos",
+				APIVersion: "v1",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: metav1.NamespaceDefault,
+				Name:      "podchaos-name",
+			},
+			Spec: v1alpha1.PodChaosSpec{
+				Selector:      v1alpha1.SelectorSpec{},
+				Mode:          v1alpha1.OnePodMode,
+				ContainerName: "container-name",
+				Scheduler:     &v1alpha1.SchedulerSpec{Cron: "@hourly"},
+			},
+		}
+
+		r := controllerkill.Reconciler{
+			Client:        fake.NewFakeClientWithScheme(scheme.Scheme, objs...),
+			EventRecorder: &record.FakeRecorder{},
+			Log:           ctrl.Log.WithName("controllers").WithName("PodChaos"),
+		}
+
+		It("ContainerKill Apply", func() {
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
+
+			err := r.Apply(context.TODO(), ctrl.Request{}, &podChaos)
+			Expect(err).ToNot(HaveOccurred())
+
+			err = r.Recover(context.TODO(), ctrl.Request{}, &podChaos)
+			Expect(err).ToNot(HaveOccurred())
+		})
+
+		It("ContainerKill Apply Error", func() {
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
+			defer mock.With("MockContainerKillError", errors.New("ContainerKillError"))()
+
+			err := r.Apply(context.TODO(), ctrl.Request{}, &podChaos)
+
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("ContainerKillError"))
+		})
+	})
+})
diff --git a/controllers/podchaos/podchaos_suite_test.go b/controllers/podchaos/podchaos_suite_test.go
new file mode 100644
index 00000000..5926e75c
--- /dev/null
+++ b/controllers/podchaos/podchaos_suite_test.go
@@ -0,0 +1,89 @@
+package podchaos_test
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/tools/record"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+	"github.com/pingcap/chaos-mesh/api/v1alpha1"
+	"github.com/pingcap/chaos-mesh/controllers/podchaos"
+)
+
+func TestPodChaos(t *testing.T) {
+	RegisterFailHandler(Fail)
+
+	RunSpecsWithDefaultAndCustomReporters(t,
+		"PodChaos Suite",
+		[]Reporter{envtest.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func(done Done) {
+	logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
+	close(done)
+}, 60)
+
+var _ = AfterSuite(func() {
+})
+
+var _ = Describe("PodChaos", func() {
+	Context("PodChaos", func() {
+		invalidDuration := "invalid duration"
+		meta := metav1.TypeMeta{
+			Kind:       "PodChaos",
+			APIVersion: "v1",
+		}
+
+		r := podchaos.Reconciler{
+			Client:        fake.NewFakeClientWithScheme(scheme.Scheme),
+			EventRecorder: &record.FakeRecorder{},
+			Log:           ctrl.Log.WithName("controllers").WithName("PodChaos"),
+		}
+
+		It("PodChaos Reconcile", func() {
+			var err error
+
+			_, err = r.Reconcile(ctrl.Request{}, &v1alpha1.PodChaos{
+				TypeMeta: meta,
+				Spec:     v1alpha1.PodChaosSpec{Scheduler: nil, Duration: &invalidDuration},
+			})
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("invalid duration"))
+
+			_, err = r.Reconcile(ctrl.Request{}, &v1alpha1.PodChaos{
+				TypeMeta: meta,
+				Spec:     v1alpha1.PodChaosSpec{Scheduler: nil, Duration: nil, Action: v1alpha1.PodKillAction},
+			})
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("unsupported chaos action"))
+			_, err = r.Reconcile(ctrl.Request{}, &v1alpha1.PodChaos{
+				TypeMeta: meta,
+				Spec:     v1alpha1.PodChaosSpec{Scheduler: nil, Duration: nil, Action: v1alpha1.ContainerKillAction},
+			})
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("unsupported chaos action"))
+			_, err = r.Reconcile(ctrl.Request{}, &v1alpha1.PodChaos{
+				TypeMeta: meta,
+				Spec:     v1alpha1.PodChaosSpec{Scheduler: nil, Duration: nil, Action: ""},
+			})
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("invalid chaos action"))
+
+			_, err = r.Reconcile(ctrl.Request{}, &v1alpha1.PodChaos{
+				TypeMeta: meta,
+				Spec:     v1alpha1.PodChaosSpec{Scheduler: &v1alpha1.SchedulerSpec{}, Duration: nil, Action: ""},
+			})
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("invalid chaos action"))
+		})
+	})
+})
diff --git a/controllers/podchaos/podfailure/podfailure_suite_test.go b/controllers/podchaos/podfailure/podfailure_suite_test.go
new file mode 100644
index 00000000..cd8b4235
--- /dev/null
+++ b/controllers/podchaos/podfailure/podfailure_suite_test.go
@@ -0,0 +1,93 @@
+package podfailure_test
+
+import (
+	"context"
+	"testing"
+
+	"k8s.io/client-go/kubernetes/scheme"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+	"github.com/pingcap/chaos-mesh/api/v1alpha1"
+	"github.com/pingcap/chaos-mesh/controllers/podchaos/podfailure"
+	. "github.com/pingcap/chaos-mesh/controllers/test"
+	"github.com/pingcap/chaos-mesh/pkg/mock"
+)
+
+func TestPodFailure(t *testing.T) {
+	RegisterFailHandler(Fail)
+
+	RunSpecsWithDefaultAndCustomReporters(t,
+		"PodFailure Suite",
+		[]Reporter{envtest.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func(done Done) {
+	logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
+
+	Expect(v1.AddToScheme(scheme.Scheme)).To(Succeed())
+	Expect(v1alpha1.AddToScheme(scheme.Scheme)).To(Succeed())
+
+	close(done)
+}, 60)
+
+var _ = AfterSuite(func() {
+})
+
+var _ = Describe("PodChaos", func() {
+	Context("PodFailure", func() {
+		objs, pods := GenerateNPods("p", 1, v1.PodRunning, metav1.NamespaceDefault, nil, nil, v1.ContainerStatus{
+			ContainerID: "fake-container-id",
+			Name:        "container-name",
+		})
+
+		podChaos := v1alpha1.PodChaos{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "PodChaos",
+				APIVersion: "v1",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: metav1.NamespaceDefault,
+				Name:      "podchaos-name",
+			},
+			Spec: v1alpha1.PodChaosSpec{
+				Selector:      v1alpha1.SelectorSpec{Namespaces: []string{metav1.NamespaceDefault}},
+				Mode:          v1alpha1.OnePodMode,
+				ContainerName: "container-name",
+				Scheduler:     &v1alpha1.SchedulerSpec{Cron: "@hourly"},
+			},
+		}
+
+		r := podfailure.Reconciler{
+			Client:        fake.NewFakeClientWithScheme(scheme.Scheme, objs...),
+			EventRecorder: &record.FakeRecorder{},
+			Log:           ctrl.Log.WithName("controllers").WithName("PodChaos"),
+		}
+
+		It("PodFailure Action", func() {
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
+			defer mock.With("MockSelectAndFilterPods", func() []v1.Pod {
+				return pods
+			})()
+
+			var err error
+
+			err = r.Apply(context.TODO(), ctrl.Request{}, &podChaos)
+			Expect(err).ToNot(HaveOccurred())
+
+			err = r.Recover(context.TODO(), ctrl.Request{}, &podChaos)
+			Expect(err).ToNot(HaveOccurred())
+		})
+
+	})
+})
diff --git a/controllers/podchaos/podkill/podkill_suite_test.go b/controllers/podchaos/podkill/podkill_suite_test.go
new file mode 100644
index 00000000..a02065d7
--- /dev/null
+++ b/controllers/podchaos/podkill/podkill_suite_test.go
@@ -0,0 +1,97 @@
+package podkill_test
+
+import (
+	"context"
+	"testing"
+
+	"k8s.io/client-go/kubernetes/scheme"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+	"github.com/pingcap/chaos-mesh/api/v1alpha1"
+	"github.com/pingcap/chaos-mesh/controllers/podchaos/podkill"
+	. "github.com/pingcap/chaos-mesh/controllers/test"
+	"github.com/pingcap/chaos-mesh/pkg/mock"
+)
+
+func TestPodKill(t *testing.T) {
+	RegisterFailHandler(Fail)
+
+	RunSpecsWithDefaultAndCustomReporters(t,
+		"PodKill Suite",
+		[]Reporter{envtest.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func(done Done) {
+	logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
+
+	Expect(v1.AddToScheme(scheme.Scheme)).To(Succeed())
+	Expect(v1alpha1.AddToScheme(scheme.Scheme)).To(Succeed())
+
+	close(done)
+}, 60)
+
+var _ = AfterSuite(func() {
+})
+
+var _ = Describe("PodChaos", func() {
+	Context("PodKill", func() {
+		objs, pods := GenerateNPods("p", 1, v1.PodRunning, metav1.NamespaceDefault, nil, nil, v1.ContainerStatus{
+			ContainerID: "fake-container-id",
+			Name:        "container-name",
+		})
+
+		podChaos := v1alpha1.PodChaos{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "PodChaos",
+				APIVersion: "v1",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: metav1.NamespaceDefault,
+				Name:      "podchaos-name",
+			},
+			Spec: v1alpha1.PodChaosSpec{
+				Selector:      v1alpha1.SelectorSpec{Namespaces: []string{metav1.NamespaceDefault}},
+				Mode:          v1alpha1.OnePodMode,
+				ContainerName: "container-name",
+				Scheduler:     &v1alpha1.SchedulerSpec{Cron: "@hourly"},
+			},
+		}
+
+		r := podkill.Reconciler{
+			Client:        fake.NewFakeClientWithScheme(scheme.Scheme, objs...),
+			EventRecorder: &record.FakeRecorder{},
+			Log:           ctrl.Log.WithName("controllers").WithName("PodChaos"),
+		}
+
+		It("PodKill Action", func() {
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
+			defer mock.With("MockSelectAndFilterPods", func() []v1.Pod {
+				return pods
+			})()
+
+			var err error
+
+			err = r.Apply(context.TODO(), ctrl.Request{}, &podChaos)
+			Expect(err).ToNot(HaveOccurred())
+
+			// pod "p0" already deleted
+			err = r.Apply(context.TODO(), ctrl.Request{}, &podChaos)
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(ContainSubstring("pods \"p0\" not found"))
+
+			err = r.Recover(context.TODO(), ctrl.Request{}, &podChaos)
+			Expect(err).ToNot(HaveOccurred())
+		})
+	})
+})
diff --git a/controllers/test/types.go b/controllers/test/types.go
index ace13d9e..35476d32 100644
--- a/controllers/test/types.go
+++ b/controllers/test/types.go
@@ -75,6 +75,7 @@ func newPod(
 	namespace string,
 	ans map[string]string,
 	ls map[string]string,
+	containerStatus v1.ContainerStatus,
 ) v1.Pod {
 	return v1.Pod{
 		TypeMeta: metav1.TypeMeta{
@@ -89,7 +90,11 @@ func newPod(
 		},
 		Status: v1.PodStatus{
 			Phase:             status,
-			ContainerStatuses: []v1.ContainerStatus{{ContainerID: "fake-container-id"}},
+			ContainerStatuses: []v1.ContainerStatus{containerStatus},
+		},
+		Spec: v1.PodSpec{
+			InitContainers: []v1.Container{{Name: "fake-name", Image: "fake-image"}},
+			Containers:     []v1.Container{{Name: "fake-name", Image: "fake-image"}},
 		},
 	}
 }
@@ -102,11 +107,12 @@ func GenerateNPods(
 	ns string,
 	ans map[string]string,
 	ls map[string]string,
+	containerStatus v1.ContainerStatus,
 ) ([]runtime.Object, []v1.Pod) {
 	var podObjects []runtime.Object
 	var pods []v1.Pod
 	for i := 0; i < n; i++ {
-		pod := newPod(fmt.Sprintf("%s%d", namePrefix, i), status, ns, ans, ls)
+		pod := newPod(fmt.Sprintf("%s%d", namePrefix, i), status, ns, ans, ls, containerStatus)
 		podObjects = append(podObjects, &pod)
 		pods = append(pods, pod)
 	}
diff --git a/controllers/timechaos/timechaos_suite_test.go b/controllers/timechaos/timechaos_suite_test.go
index 03f665f6..ede02b9e 100644
--- a/controllers/timechaos/timechaos_suite_test.go
+++ b/controllers/timechaos/timechaos_suite_test.go
@@ -5,12 +5,13 @@ import (
 	"errors"
 	"testing"
 
+	"k8s.io/client-go/kubernetes/scheme"
+
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/record"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -34,6 +35,9 @@ func TestTimechaos(t *testing.T) {
 
 var _ = BeforeSuite(func(done Done) {
 	logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
+
+	Expect(v1.AddToScheme(scheme.Scheme)).To(Succeed())
+
 	close(done)
 }, 60)
 
@@ -41,25 +45,16 @@ var _ = AfterSuite(func() {
 })
 
 var _ = Describe("TimeChaos", func() {
-	BeforeEach(func() {
-		// Add any setup steps that needs to be executed before each test
-	})
-
-	AfterEach(func() {
-		// Add any teardown steps that needs to be executed after each test
-	})
-
-	// Add Tests for OpenAPI validation (or additional CRD features) specified in
-	// your API definition.
-	// Avoid adding tests for vanilla CRUD operations because they would
-	// test Kubernetes API server, which isn't the goal here.
 	Context("TimeChaos", func() {
-		podObjects, pods := GenerateNPods("p", 1, v1.PodRunning, metav1.NamespaceDefault, nil, map[string]string{"l1": "l1"})
-
-		mock.With("MockSelectAndGeneratePods", func() []v1.Pod {
-			return pods
-		})
-		mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})
+		podObjects, pods := GenerateNPods(
+			"p",
+			1,
+			v1.PodRunning,
+			metav1.NamespaceDefault,
+			nil,
+			map[string]string{"l1": "l1"},
+			v1.ContainerStatus{ContainerID: "fake-container-id"},
+		)
 
 		duration := "invalid_duration"
 
@@ -69,7 +64,7 @@ var _ = Describe("TimeChaos", func() {
 				APIVersion: "v1",
 			},
 			Spec: v1alpha1.TimeChaosSpec{
-				Mode:       "FixedPodMode",
+				Mode:       v1alpha1.AllPodMode,
 				Value:      "0",
 				Selector:   v1alpha1.SelectorSpec{Namespaces: []string{metav1.NamespaceDefault}},
 				TimeOffset: v1alpha1.TimeOffset{},
@@ -78,35 +73,56 @@ var _ = Describe("TimeChaos", func() {
 			},
 		}
 
-		It("TimeChaos Action", func() {
-			scheme := runtime.NewScheme()
-			Expect(v1.AddToScheme(scheme)).To(Succeed())
-
-			r := Reconciler{
-				Client:        fake.NewFakeClientWithScheme(scheme, podObjects...),
-				EventRecorder: &record.FakeRecorder{},
-				Log:           ctrl.Log.WithName("controllers").WithName("TimeChaos"),
-			}
+		r := Reconciler{
+			Client:        fake.NewFakeClientWithScheme(scheme.Scheme, podObjects...),
+			EventRecorder: &record.FakeRecorder{},
+			Log:           ctrl.Log.WithName("controllers").WithName("TimeChaos"),
+		}
 
-			var err error
+		It("TimeChaos Apply", func() {
+			defer mock.With("MockSelectAndFilterPods", func() []v1.Pod {
+				return pods
+			})()
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
 
-			err = r.Apply(context.TODO(), ctrl.Request{}, &timechaos)
+			err := r.Apply(context.TODO(), ctrl.Request{}, &timechaos)
 
 			Expect(err).ToNot(HaveOccurred())
+		})
 
-			mock.With("MockSetTimeOffsetError", errors.New("SetTimeOffsetError"))
+		It("TimeChaos Apply Error", func() {
+			defer mock.With("MockSelectAndFilterPods", func() []v1.Pod {
+				return pods
+			})()
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
+			defer mock.With("MockSetTimeOffsetError", errors.New("SetTimeOffsetError"))()
 
-			err = r.Apply(context.TODO(), ctrl.Request{}, &timechaos)
+			err := r.Apply(context.TODO(), ctrl.Request{}, &timechaos)
 
 			Expect(err).To(HaveOccurred())
 			Expect(err.Error()).To(ContainSubstring("SetTimeOffsetError"))
 
-			err = r.Recover(context.TODO(), ctrl.Request{}, &timechaos)
+		})
+
+		It("TimeChaos Recover", func() {
+			defer mock.With("MockSelectAndFilterPods", func() []v1.Pod {
+				return pods
+			})()
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
+
+			err := r.Recover(context.TODO(), ctrl.Request{}, &timechaos)
 			Expect(err).ToNot(HaveOccurred())
+		})
 
-			mock.With("MockSetTimeOffsetError", nil)
-			err = r.Apply(context.TODO(), ctrl.Request{}, &timechaos)
-			mock.With("MockRecoverTimeOffsetError", errors.New("RecoverTimeOffsetError"))
+		It("TimeChaos Recover Error", func() {
+			defer mock.With("MockSelectAndFilterPods", func() []v1.Pod {
+				return pods
+			})()
+			defer mock.With("MockChaosDaemonClient", &MockChaosDaemonClient{})()
+			defer mock.With("MockRecoverTimeOffsetError", errors.New("RecoverTimeOffsetError"))()
+
+			err := r.Apply(context.TODO(), ctrl.Request{}, &timechaos)
+			Expect(err).ToNot(HaveOccurred())
 
 			err = r.Recover(context.TODO(), ctrl.Request{}, &timechaos)
 
diff --git a/pkg/utils/selector_test.go b/pkg/utils/selector_test.go
index 0707a7c6..7658912d 100644
--- a/pkg/utils/selector_test.go
+++ b/pkg/utils/selector_test.go
@@ -86,7 +86,7 @@ func TestSelectPods(t *testing.T) {
 	}
 
 	for _, tc := range tcs {
-		filteredPods, err := SelectPods(context.Background(), c, tc.selector)
+		filteredPods, err := selectPods(context.Background(), c, tc.selector)
 		g.Expect(err).ShouldNot(HaveOccurred(), tc.name)
 		g.Expect(len(filteredPods)).To(Equal(len(tc.expectedPods)), tc.name)
 	}