From 6b5f77b16338827f784714be9774588f72041218 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 4 Jan 2023 21:30:49 +0100 Subject: [PATCH] e2e framework/pod: add gomega matchers They can be used for polling with a get function and gomega.Eventually or gomega.Consistently. --- test/e2e/framework/pod/wait.go | 35 +++ .../onsi/gomega/gcustom/make_matcher.go | 269 ++++++++++++++++++ vendor/modules.txt | 1 + 3 files changed, 305 insertions(+) create mode 100644 vendor/github.com/onsi/gomega/gcustom/make_matcher.go diff --git a/test/e2e/framework/pod/wait.go b/test/e2e/framework/pod/wait.go index b2d8327c2bb..2493babf873 100644 --- a/test/e2e/framework/pod/wait.go +++ b/test/e2e/framework/pod/wait.go @@ -26,6 +26,9 @@ import ( "time" "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/onsi/gomega/gcustom" + "github.com/onsi/gomega/types" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -165,6 +168,38 @@ func errorBadPodsStates(badPods []v1.Pod, desiredPods int, ns, desiredState stri return TimeoutError(errStr) } +// BeRunningNoRetries verifies that a pod starts running. It's a permanent +// failure when the pod enters some other permanent phase. +func BeRunningNoRetries() types.GomegaMatcher { + return gomega.And( + // This additional matcher checks for the final error condition. + gcustom.MakeMatcher(func(pod *v1.Pod) (bool, error) { + switch pod.Status.Phase { + case v1.PodFailed, v1.PodSucceeded: + return false, gomega.StopTrying(fmt.Sprintf("Expected pod to reach phase %q, got final phase %q instead.", v1.PodRunning, pod.Status.Phase)) + default: + return true, nil + } + }), + BeInPhase(v1.PodRunning), + ) +} + +// BeInPhase matches if pod.status.phase is the expected phase. +func BeInPhase(phase v1.PodPhase) types.GomegaMatcher { + // A simple implementation of this would be: + // return gomega.HaveField("Status.Phase", phase) + // + // But that produces a fairly generic + // Value for field 'Status.Phase' failed to satisfy matcher. + // failure message and doesn't show the pod. We can do better than + // that with a custom matcher. + + return gcustom.MakeMatcher(func(pod *v1.Pod) (bool, error) { + return pod.Status.Phase == phase, nil + }).WithTemplate("Expected Pod {{.To}} be in {{format .Data}}\nGot instead:\n{{.FormattedActual}}").WithTemplateData(phase) +} + // WaitForPodsRunningReady waits up to timeout to ensure that all pods in // namespace ns are either running and ready, or failed but controlled by a // controller. Also, it ensures that at least minPods are running and diff --git a/vendor/github.com/onsi/gomega/gcustom/make_matcher.go b/vendor/github.com/onsi/gomega/gcustom/make_matcher.go new file mode 100644 index 00000000000..f3cd51ff9ba --- /dev/null +++ b/vendor/github.com/onsi/gomega/gcustom/make_matcher.go @@ -0,0 +1,269 @@ +/* +package gcustom provides a simple mechanism for creating custom Gomega matchers +*/ +package gcustom + +import ( + "fmt" + "reflect" + "strings" + "text/template" + + "github.com/onsi/gomega/format" +) + +var interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() +var errInterface = reflect.TypeOf((*error)(nil)).Elem() + +var defaultTemplate = template.Must(ParseTemplate("{{if .Failure}}Custom matcher failed for:{{else}}Custom matcher succeeded (but was expected to fail) for:{{end}}\n{{.FormattedActual}}")) + +func formatObject(object any, indent ...uint) string { + indentation := uint(0) + if len(indent) > 0 { + indentation = indent[0] + } + return format.Object(object, indentation) +} + +/* +ParseTemplate allows you to precompile templates for MakeMatcher's custom matchers. + +Use ParseTemplate if you are concerned about performance and would like to avoid repeatedly parsing failure message templates. The data made available to the template is documented in the WithTemplate() method of CustomGomegaMatcher. + +Once parsed you can pass the template in either as an argument to MakeMatcher(matchFunc,