From d1a56044b55eb44d1c6654ac124fd1a5a3f1dc0e Mon Sep 17 00:00:00 2001 From: Jay Pipes Date: Tue, 7 Oct 2025 09:36:11 -0400 Subject: [PATCH] support `assert.require` to fail test scenario Adds support for the `assert.require` field which contains a boolean value that instructs the gdt scenario to exit with a failure when an assertion fails. This is equivalent to the difference between testify assert and testify require or the Go testing.T.Error versus testing.T.Fatal. Issue gdt-dev/gdt#66 Signed-off-by: Jay Pipes --- README.md | 4 ++++ assertions.go | 3 +++ eval.go | 9 ++++++++- go.mod | 2 +- go.sum | 4 ++-- parse.go | 21 +++++++++++++++++++++ spec.go | 4 ++++ 7 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b49087a..1b7a293 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,10 @@ matches some expectation: Kubernetes resource returned in a `kubectl get` response. * `assert`: (optional) object containing assertions to make about the action performed by the test. +* `assert.require`: (optional) a boolean indicating whether a failed assertion + will cause the test scenario's execution to stop. The default behaviour of + `gdt` is to continue execution of subsequent test specs in a test scenario when + an assertion fails. * `assert.error`: (optional) string to match a returned error from the Kubernetes API server. * `assert.len`: (optional) int with the expected number of items returned. diff --git a/assertions.go b/assertions.go index 28d6f02..b75ca9b 100644 --- a/assertions.go +++ b/assertions.go @@ -21,6 +21,9 @@ import ( // Expect contains one or more assertions about a kube client call type Expect struct { + // Require indicates that any failed assertion should stop the execution of + // the test scenario in which the test spec is contained. + Require bool `yaml:"require,omitempty"` // Error is a string that is expected to be returned as an error string // from the client call // TODO(jaypipes): Make this polymorphic to be either a shortcut string diff --git a/eval.go b/eval.go index 5dd1ed1..4c684bc 100644 --- a/eval.go +++ b/eval.go @@ -54,7 +54,14 @@ func (s *Spec) Eval(ctx context.Context) (*api.Result, error) { } return res, nil } - return api.NewResult(api.WithFailures(a.Failures()...)), nil + stopOnFail := false + if s.Assert != nil { + stopOnFail = s.Assert.Require + } + return api.NewResult( + api.WithStopOnFail(stopOnFail), + api.WithFailures(a.Failures()...), + ), nil } // cleanupAutoNamespace returns a cleanup function that deletes the diff --git a/go.mod b/go.mod index 9d50d2a..98049ee 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.3 require ( github.com/cenkalti/backoff v2.2.1+incompatible - github.com/gdt-dev/core v1.10.0 + github.com/gdt-dev/core v1.10.3 github.com/samber/lo v1.51.0 github.com/stretchr/testify v1.11.1 github.com/theory/jsonpath v0.10.1 diff --git a/go.sum b/go.sum index 0d38a07..795a654 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gdt-dev/core v1.10.0 h1:yX0MG2Tt+O34JGDFWcS63LV47lWpizto4HAyR/ugAC0= -github.com/gdt-dev/core v1.10.0/go.mod h1:Bw8J6kUW0b7MUL8qW5e7qSbxb4SI9EAWQ0a4cAoPVpo= +github.com/gdt-dev/core v1.10.3 h1:4cl8h8/SeL5oBzDFyg7WhZbRcLRBb6SUY57L5Swju2A= +github.com/gdt-dev/core v1.10.3/go.mod h1:Bw8J6kUW0b7MUL8qW5e7qSbxb4SI9EAWQ0a4cAoPVpo= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= diff --git a/parse.go b/parse.go index 723b963..2bead9e 100644 --- a/parse.go +++ b/parse.go @@ -7,6 +7,7 @@ package kube import ( "fmt" "os" + "strconv" "strings" "github.com/gdt-dev/core/api" @@ -252,6 +253,16 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { return err } s.Assert = e + case "require": + if valNode.Kind != yaml.MappingNode { + return parse.ExpectedMapAt(valNode) + } + var e *Expect + if err := valNode.Decode(&e); err != nil { + return err + } + e.Require = true + s.Assert = e case "kube.get", "kube.create", "kube.delete", "kube.apply": continue default: @@ -394,6 +405,16 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { key := keyNode.Value valNode := node.Content[i+1] switch key { + case "require", "stop-on-fail", "stop_on_fail", "stop.on.fail", + "fail-stop", "fail.stop", "fail_stop": + if valNode.Kind != yaml.ScalarNode { + return parse.ExpectedScalarAt(valNode) + } + req, err := strconv.ParseBool(valNode.Value) + if err != nil { + return parse.ExpectedBoolAt(valNode) + } + e.Require = req case "error": if valNode.Kind != yaml.ScalarNode { return parse.ExpectedScalarAt(valNode) diff --git a/spec.go b/spec.go index 4351d52..63c4742 100644 --- a/spec.go +++ b/spec.go @@ -75,6 +75,10 @@ type Spec struct { // having such a label. // * the string `--all` to delete all resources of that kind. KubeDelete string `yaml:"kube.delete,omitempty"` + // Require is an object containing the conditions that the Spec will + // assert. If any condition fails, the test scenario execution will stop + // and be marked as failed. + Require *Expect `yaml:"require,omitempty"` // Assert houses the various assertions to be made about the kube client // call (Create, Apply, Get, etc) // TODO(jaypipes): Make this polymorphic to be either a single assertion