Skip to content

Commit 5e33c75

Browse files
committed
DeferCleanup supports functions with multiple-return values
...and only checks if the last return value is an error to determine wether to succeed or fail.
1 parent bf78c28 commit 5e33c75

File tree

5 files changed

+55
-24
lines changed

5 files changed

+55
-24
lines changed

core_dsl.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -724,10 +724,10 @@ DeferCleanup can be called within any Setup or Subject node to register a cleanu
724724
725725
DeferCleanup can be passed:
726726
1. A function that takes no arguments and returns no values.
727-
2. A function that returns an error (in which case it will assert that the returned error was nil, or it will fail the spec).
728-
3. A function that takes a context.Context or SpecContext (and optionally returns an error). The resulting cleanup node is deemed interruptible and the passed-in context will be cancelled in the event of a timeout or interrupt.
729-
4. A function that takes arguments (and optionally returns an error) followed by a list of arguments to pass to the function.
730-
5. A function that takes SpecContext and a list of arguments (and optionally returns an error) followed by a list of arguments to pass to the function.
727+
2. A function that returns multiple values. `DeferCleanup` will ignore all these return values except for the last one. If this last return value is a non-nil error `DeferCleanup` will fail the spec).
728+
3. A function that takes a context.Context or SpecContext (and optionally returns multiple values). The resulting cleanup node is deemed interruptible and the passed-in context will be cancelled in the event of a timeout or interrupt.
729+
4. A function that takes arguments (and optionally returns multiple values) followed by a list of arguments to pass to the function.
730+
5. A function that takes SpecContext and a list of arguments (and optionally returns multiple values) followed by a list of arguments to pass to the function.
731731
732732
For example:
733733

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ As you can see, `DeferCleanup()` can be called inside any setup or subject nodes
982982
983983
`DeferCleanup` has a few more tricks up its sleeve.
984984

985-
As shown above `DeferCleanup` can be passed a function that takes no arguments and returns no value. You can also pass a function that returns a single value. `DeferCleanup` interprets this value as an error and fails the spec if the error is non-nil - a common go pattern. This allows us to rewrite our example as:
985+
As shown above `DeferCleanup` can be passed a function that takes no arguments and returns no value. You can also pass a function that returns values. `DeferCleanup` ignores all these return value except for the last. If the last return value is a non-nil error - a common go pattern - `DeferCleanup` will fail the spec. This allows us to rewrite our example as:
986986

987987
```go
988988
Describe("Reporting book weight", func() {

internal/internal_integration/cleanup_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ var _ = Describe("Cleanup", func() {
110110
})
111111
})
112112

113+
Context("because of a returned error, for a multi-return function", func() {
114+
BeforeEach(func() {
115+
success, _ := RunFixture("cleanup failure", func() {
116+
BeforeEach(rt.T("BE", C("C-BE")))
117+
It("A", rt.T("A", func() {
118+
DeferCleanup(func() (string, error) {
119+
rt.Run("C-A")
120+
return "ok", fmt.Errorf("fail")
121+
})
122+
}))
123+
})
124+
Ω(success).Should(BeFalse())
125+
})
126+
127+
It("reports a failure", func() {
128+
Ω(rt).Should(HaveTracked("BE", "A", "C-A", "C-BE"))
129+
Ω(reporter.Did.Find("A")).Should(HaveFailed("DeferCleanup callback returned error: fail", FailureNodeType(types.NodeTypeCleanupAfterEach), types.FailureNodeAtTopLevel))
130+
})
131+
})
132+
113133
Context("at the suite level", func() {
114134
BeforeEach(func() {
115135
success, _ := RunFixture("cleanup failure", func() {

internal/node.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ func extractSynchronizedBeforeSuiteAllProcsBody(arg interface{}) (func(SpecConte
508508
}, hasContext
509509
}
510510

511+
var errInterface = reflect.TypeOf((*error)(nil)).Elem()
512+
511513
func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(string, types.CodeLocation), args ...interface{}) (Node, []error) {
512514
decorations, remainingArgs := PartitionDecorations(args...)
513515
baseOffset := 2
@@ -530,7 +532,7 @@ func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(stri
530532
}
531533

532534
callback := reflect.ValueOf(remainingArgs[0])
533-
if !(callback.Kind() == reflect.Func && callback.Type().NumOut() <= 1) {
535+
if !(callback.Kind() == reflect.Func) {
534536
return Node{}, []error{types.GinkgoErrors.DeferCleanupInvalidFunction(cl)}
535537
}
536538

@@ -550,8 +552,12 @@ func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(stri
550552
}
551553

552554
handleFailure := func(out []reflect.Value) {
553-
if len(out) == 1 && !out[0].IsNil() {
554-
fail(fmt.Sprintf("DeferCleanup callback returned error: %v", out[0]), cl)
555+
if len(out) == 0 {
556+
return
557+
}
558+
last := out[len(out)-1]
559+
if last.Type().Implements(errInterface) && !last.IsNil() {
560+
fail(fmt.Sprintf("DeferCleanup callback returned error: %v", last), cl)
555561
}
556562
}
557563

internal/node_test.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -894,23 +894,28 @@ var _ = Describe("Node", func() {
894894
})
895895
})
896896

897-
Context("when passed a function that returns too many values", func() {
898-
It("errors", func() {
899-
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() (int, error) {
900-
return 0, nil
897+
Context("when passed a function that does not return", func() {
898+
It("creates a body that runs the function and never calls the fail handler", func() {
899+
didRun := false
900+
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() {
901+
didRun = true
901902
})
902-
Ω(node.IsZero()).Should(BeTrue())
903-
Ω(errs).Should(ConsistOf(types.GinkgoErrors.DeferCleanupInvalidFunction(cl)))
903+
Ω(node.CodeLocation).Should(Equal(cl))
904+
Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid))
905+
Ω(errs).Should(BeEmpty())
906+
907+
node.Body(internal.NewSpecContext(nil))
908+
Ω(didRun).Should(BeTrue())
904909
Ω(capturedFailure).Should(BeZero())
905910
Ω(capturedCL).Should(BeZero())
906911
})
907912
})
908-
909-
Context("when passed a function that does not return", func() {
913+
Context("when passed a function that returns somethign that isn't an error", func() {
910914
It("creates a body that runs the function and never calls the fail handler", func() {
911915
didRun := false
912-
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() {
916+
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() (string, int) {
913917
didRun = true
918+
return "not-an-error", 17
914919
})
915920
Ω(node.CodeLocation).Should(Equal(cl))
916921
Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid))
@@ -923,12 +928,12 @@ var _ = Describe("Node", func() {
923928
})
924929
})
925930

926-
Context("when passed a function that returns nil", func() {
931+
Context("when passed a function that returns a nil error", func() {
927932
It("creates a body that runs the function and does not call the fail handler", func() {
928933
didRun := false
929-
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() error {
934+
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() (string, int, error) {
930935
didRun = true
931-
return nil
936+
return "not-an-error", 17, nil
932937
})
933938
Ω(node.CodeLocation).Should(Equal(cl))
934939
Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid))
@@ -941,12 +946,12 @@ var _ = Describe("Node", func() {
941946
})
942947
})
943948

944-
Context("when passed a function that returns an error", func() {
945-
It("creates a body that runs the function and does not call the fail handler", func() {
949+
Context("when passed a function that returns an error for its final return value", func() {
950+
It("creates a body that runs the function and calls the fail handler", func() {
946951
didRun := false
947-
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() error {
952+
node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() (string, int, error) {
948953
didRun = true
949-
return fmt.Errorf("welp")
954+
return "not-an-error", 17, fmt.Errorf("welp")
950955
})
951956
Ω(node.CodeLocation).Should(Equal(cl))
952957
Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid))

0 commit comments

Comments
 (0)