From 74a42a02f38f32299f55aae71f89d53b68344516 Mon Sep 17 00:00:00 2001 From: fossedihelm Date: Thu, 17 Feb 2022 14:37:23 +0100 Subject: [PATCH] test: enrich phase matcher When matcher fails it shows the `kind` and the `name` of the object (if exist) Signed-off-by: fossedihelm --- tests/framework/matcher/BUILD.bazel | 3 + tests/framework/matcher/getter.go | 8 ++ tests/framework/matcher/helper/BUILD.bazel | 4 + tests/framework/matcher/helper/reflect.go | 20 +++- tests/framework/matcher/owner.go | 23 +---- tests/framework/matcher/phase.go | 37 ++++++- tests/framework/matcher/phase_test.go | 106 +++++++++++++++++++++ 7 files changed, 180 insertions(+), 21 deletions(-) diff --git a/tests/framework/matcher/BUILD.bazel b/tests/framework/matcher/BUILD.bazel index dc6288a7056e..33a5c390bcb1 100644 --- a/tests/framework/matcher/BUILD.bazel +++ b/tests/framework/matcher/BUILD.bazel @@ -38,12 +38,15 @@ go_test( ], embed = [":go_default_library"], deps = [ + "//staging/src/kubevirt.io/api/core/v1:go_default_library", "//staging/src/kubevirt.io/client-go/testutils:go_default_library", + "//tests/framework/matcher/helper:go_default_library", "//vendor/github.com/onsi/ginkgo:go_default_library", "//vendor/github.com/onsi/ginkgo/extensions/table:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library", "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1:go_default_library", ], ) diff --git a/tests/framework/matcher/getter.go b/tests/framework/matcher/getter.go index fe42b203c425..835cb30ea522 100644 --- a/tests/framework/matcher/getter.go +++ b/tests/framework/matcher/getter.go @@ -29,6 +29,8 @@ func ThisPodWith(namespace string, name string) func() (*v1.Pod, error) { if errors.IsNotFound(err) { return nil, nil } + //Since https://github.com/kubernetes/client-go/issues/861 we manually add the Kind + p.Kind = "Pod" return } } @@ -101,6 +103,8 @@ func ThisDVWith(namespace string, name string) func() (*v1beta1.DataVolume, erro if errors.IsNotFound(err) { return nil, nil } + //Since https://github.com/kubernetes/client-go/issues/861 we manually add the Kind + p.Kind = "DataVolume" return } } @@ -121,6 +125,8 @@ func ThisPVCWith(namespace string, name string) func() (*v1.PersistentVolumeClai if errors.IsNotFound(err) { return nil, nil } + //Since https://github.com/kubernetes/client-go/issues/861 we manually add the Kind + p.Kind = "PersistentVolumeClaim" return } } @@ -156,6 +162,8 @@ func ThisDeploymentWith(namespace string, name string) func() (*k8sv1.Deployment if errors.IsNotFound(err) { return nil, nil } + //Since https://github.com/kubernetes/client-go/issues/861 we manually add the Kind + p.Kind = "Deployment" return } } diff --git a/tests/framework/matcher/helper/BUILD.bazel b/tests/framework/matcher/helper/BUILD.bazel index e3004e463807..7b57b19c0080 100644 --- a/tests/framework/matcher/helper/BUILD.bazel +++ b/tests/framework/matcher/helper/BUILD.bazel @@ -5,4 +5,8 @@ go_library( srcs = ["reflect.go"], importpath = "kubevirt.io/kubevirt/tests/framework/matcher/helper", visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + ], ) diff --git a/tests/framework/matcher/helper/reflect.go b/tests/framework/matcher/helper/reflect.go index 644bb5fc5a13..63650bc0d1f9 100644 --- a/tests/framework/matcher/helper/reflect.go +++ b/tests/framework/matcher/helper/reflect.go @@ -1,6 +1,12 @@ package helper -import "reflect" +import ( + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) func IsNil(actual interface{}) bool { return actual == nil || (reflect.ValueOf(actual).Kind() == reflect.Ptr && reflect.ValueOf(actual).IsNil()) @@ -45,3 +51,15 @@ func MatchElementsInSlice(actual interface{}, matcher func(actual interface{}) ( }) return success, err } + +func ToUnstructured(actual interface{}) (*unstructured.Unstructured, error) { + if IsNil(actual) { + return nil, fmt.Errorf("object does not exist") + } + actual = ToPointer(actual) + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(actual) + if err != nil { + return nil, err + } + return &unstructured.Unstructured{Object: obj}, nil +} diff --git a/tests/framework/matcher/owner.go b/tests/framework/matcher/owner.go index 8ef3eecfcbc2..4566b2fa99a5 100644 --- a/tests/framework/matcher/owner.go +++ b/tests/framework/matcher/owner.go @@ -3,11 +3,9 @@ package matcher import ( "fmt" - "github.com/onsi/gomega/types" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "kubevirt.io/kubevirt/tests/framework/matcher/helper" + + "github.com/onsi/gomega/types" ) func BeOwned() types.GomegaMatcher { @@ -22,7 +20,7 @@ type ownedMatcher struct { } func (o ownedMatcher) Match(actual interface{}) (success bool, err error) { - u, err := toUnstructured(actual) + u, err := helper.ToUnstructured(actual) if err != nil { return false, nil } @@ -33,7 +31,7 @@ func (o ownedMatcher) Match(actual interface{}) (success bool, err error) { } func (o ownedMatcher) FailureMessage(actual interface{}) (message string) { - u, err := toUnstructured(actual) + u, err := helper.ToUnstructured(actual) if err != nil { return err.Error() } @@ -41,20 +39,9 @@ func (o ownedMatcher) FailureMessage(actual interface{}) (message string) { } func (o ownedMatcher) NegatedFailureMessage(actual interface{}) (message string) { - u, err := toUnstructured(actual) + u, err := helper.ToUnstructured(actual) if err != nil { return err.Error() } return fmt.Sprintf("Expected owner references to be empty, but got '%v'", u) } - -func toUnstructured(actual interface{}) (*unstructured.Unstructured, error) { - if helper.IsNil(actual) { - return nil, fmt.Errorf("object does not exist") - } - obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(actual) - if err != nil { - return nil, err - } - return &unstructured.Unstructured{Object: obj}, nil -} diff --git a/tests/framework/matcher/phase.go b/tests/framework/matcher/phase.go index 4be808169ffe..53acd1481d0c 100644 --- a/tests/framework/matcher/phase.go +++ b/tests/framework/matcher/phase.go @@ -73,11 +73,15 @@ func (p phaseMatcher) FailureMessage(actual interface{}) (message string) { return fmt.Sprintf("expected phases to be in %v but got %v", expectedPhase, collectPhasesForPrinting(actual)) } + objectInfo, err := getObjectKindAndName(actual) + if err != nil { + return err.Error() + } phase, err := getCurrentPhase(actual) if err != nil { return err.Error() } - return fmt.Sprintf("expected phase is '%v' but got '%v'", expectedPhase, phase) + return fmt.Sprintf("%s expected phase is '%v' but got '%v'", objectInfo, expectedPhase, phase) } func (p phaseMatcher) NegatedFailureMessage(actual interface{}) (message string) { @@ -88,11 +92,32 @@ func (p phaseMatcher) NegatedFailureMessage(actual interface{}) (message string) if helper.IsSlice(actual) { return fmt.Sprintf("expected phases to not be in %v but got %v", expectedPhase, collectPhasesForPrinting(actual)) } + objectInfo, err := getObjectKindAndName(actual) + if err != nil { + return err.Error() + } phase, err := getCurrentPhase(actual) if err != nil { return err.Error() } - return fmt.Sprintf("expected phase '%v' to not match '%v'", expectedPhase, phase) + return fmt.Sprintf("%s expected phase '%v' to not match '%v'", objectInfo, expectedPhase, phase) +} + +func getObjectKindAndName(actual interface{}) (string, error) { + u, err := helper.ToUnstructured(actual) + if err != nil { + return "", err + } + objectInfo := "" + if u != nil { + if u.GetKind() != "" { + objectInfo = fmt.Sprintf("%s/", u.GetKind()) + } + if u.GetName() != "" { + objectInfo = fmt.Sprintf("%s%s", objectInfo, u.GetName()) + } + } + return objectInfo, nil } func getCurrentPhase(actual interface{}) (string, error) { @@ -115,6 +140,14 @@ func collectPhasesForPrinting(actual interface{}) (phases []string) { if err != nil { phase = err.Error() } + objectInfo, err := getObjectKindAndName(value) + if err != nil { + phase = err.Error() + } + if objectInfo != "" { + phase = fmt.Sprintf("%s:%s", objectInfo, phase) + } + phases = append(phases, phase) return true }) diff --git a/tests/framework/matcher/phase_test.go b/tests/framework/matcher/phase_test.go index 19b6c8889754..ec579df48dc7 100644 --- a/tests/framework/matcher/phase_test.go +++ b/tests/framework/matcher/phase_test.go @@ -1,10 +1,17 @@ package matcher import ( + "fmt" + . "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" + v12 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v13 "kubevirt.io/api/core/v1" + "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "kubevirt.io/kubevirt/tests/framework/matcher/helper" ) var _ = Describe("Matcher", func() { @@ -24,6 +31,69 @@ var _ = Describe("Matcher", func() { }, } + var nameAndKindPod = &v1.Pod{ + ObjectMeta: v12.ObjectMeta{ + Name: "testpod", + }, + TypeMeta: v12.TypeMeta{ + Kind: "Pod", + }, + } + + var nameAndKindDV = &v1beta1.DataVolume{ + ObjectMeta: v12.ObjectMeta{ + Name: "testdv", + }, + TypeMeta: v12.TypeMeta{ + Kind: "DataVolume", + }, + } + + var nameAndKindVMI = &v13.VirtualMachineInstance{ + ObjectMeta: v12.ObjectMeta{ + Name: "testvmi", + }, + TypeMeta: v12.TypeMeta{ + Kind: "VirtualMachineInstance", + }, + } + + var onlyKindPod = &v1.Pod{ + TypeMeta: v12.TypeMeta{ + Kind: "Pod", + }, + } + + var onlyKindDV = &v1beta1.DataVolume{ + TypeMeta: v12.TypeMeta{ + Kind: "DataVolume", + }, + } + + var onlyKindVMI = &v13.VirtualMachineInstance{ + TypeMeta: v12.TypeMeta{ + Kind: "VirtualMachineInstance", + }, + } + + var onlyNamePod = &v1.Pod{ + ObjectMeta: v12.ObjectMeta{ + Name: "testpod", + }, + } + + var onlyNameDV = &v1beta1.DataVolume{ + ObjectMeta: v12.ObjectMeta{ + Name: "testdv", + }, + } + + var onlyNameVMI = &v13.VirtualMachineInstance{ + ObjectMeta: v12.ObjectMeta{ + Name: "testvmi", + }, + } + table.DescribeTable("should work on a pod", func(exptectedPhase interface{}, pod interface{}, match bool) { success, err := BeInPhase(exptectedPhase).Match(pod) Expect(err).ToNot(HaveOccurred()) @@ -59,4 +129,40 @@ var _ = Describe("Matcher", func() { table.Entry("cope with a non-stringable object as expected phase", nil, []*v1.Pod{runningPod}, false), table.Entry("with expected phase not match the pod phase", "Succeeded", []*v1.Pod{runningPod}, false), ) + + table.DescribeTable("should print kind and name of the object depending on fields", func(object interface{}, kind string, name string) { + unstructured, err := helper.ToUnstructured(object) + Expect(err).ToNot(HaveOccurred()) + Expect(unstructured.GetKind()).To(Equal(kind)) + Expect(unstructured.GetName()).To(Equal(name)) + if kind != "" && name != "" { + Expect(BeInPhase("testPhase").FailureMessage(object)).Should(HavePrefix(fmt.Sprintf("%s/%s", unstructured.GetKind(), unstructured.GetName()))) + Expect(BeInPhase("testPhase").NegatedFailureMessage(object)).Should(HavePrefix(fmt.Sprintf("%s/%s", unstructured.GetKind(), unstructured.GetName()))) + } else if kind != "" { + Expect(BeInPhase("testPhase").FailureMessage(object)).Should(HavePrefix(fmt.Sprintf("%s/", unstructured.GetKind()))) + Expect(BeInPhase("testPhase").NegatedFailureMessage(object)).Should(HavePrefix(fmt.Sprintf("%s/", unstructured.GetKind()))) + } else if name != "" { + Expect(BeInPhase("testPhase").FailureMessage(object)).Should(HavePrefix(fmt.Sprintf("%s", unstructured.GetName()))) + Expect(BeInPhase("testPhase").NegatedFailureMessage(object)).Should(HavePrefix(fmt.Sprintf("%s", unstructured.GetName()))) + } else { + Expect(BeInPhase("testPhase").FailureMessage(object)).ShouldNot(HavePrefix(fmt.Sprintf("%s/", unstructured.GetKind()))) + Expect(BeInPhase("testPhase").NegatedFailureMessage(object)).ShouldNot(HavePrefix(fmt.Sprintf("%s/", unstructured.GetKind()))) + Expect(BeInPhase("testPhase").FailureMessage(object)).Should(HavePrefix(" expected")) + Expect(BeInPhase("testPhase").NegatedFailureMessage(object)).Should(HavePrefix(" expected")) + } + + }, + table.Entry("with a Pod having name and kind", nameAndKindPod, nameAndKindPod.Kind, nameAndKindPod.Name), + table.Entry("with a DataVolume having name and kind", nameAndKindDV, nameAndKindDV.Kind, nameAndKindDV.Name), + table.Entry("with a VirtualMachineInstance having name and kind", nameAndKindVMI, nameAndKindVMI.Kind, nameAndKindVMI.Name), + table.Entry("with a Pod having only kind", onlyKindPod, onlyKindPod.Kind, onlyKindPod.Name), + table.Entry("with a DataVolume having only kind", onlyKindDV, onlyKindDV.Kind, onlyKindDV.Name), + table.Entry("with a VirtualMachineInstance having only kind", onlyKindVMI, onlyKindVMI.Kind, onlyKindVMI.Name), + table.Entry("with a Pod having only name", onlyNamePod, onlyNamePod.Kind, onlyNamePod.Name), + table.Entry("with a DataVolume having only name", onlyNameDV, onlyNameDV.Kind, onlyNameDV.Name), + table.Entry("with a VirtualMachineInstance having only name", onlyNameVMI, onlyNameVMI.Kind, onlyNameVMI.Name), + table.Entry("with a Pod having no kind and name", &v1.Pod{}, "", ""), + table.Entry("with a DataVolume having no kind and name", &v1beta1.DataVolume{}, "", ""), + table.Entry("with a VirtualMachineInstance having no kind and name", &v13.VirtualMachineInstance{}, "", ""), + ) })