From c4e219ff5f317e6f0b78e30c2554088ee3ea2f92 Mon Sep 17 00:00:00 2001 From: Eugene Nosenko Date: Mon, 26 Feb 2024 09:25:13 +0100 Subject: [PATCH] add SpecContext to other reporting nodes and update tests --- .../internal_integration/report_each_test.go | 22 ++++++-- .../internal_integration/report_suite_test.go | 36 +++++++++++-- internal/node.go | 21 +++++--- internal/node_test.go | 8 +-- internal/suite.go | 12 ++--- reporting_dsl.go | 51 +++++++++++++++---- 6 files changed, 113 insertions(+), 37 deletions(-) diff --git a/internal/internal_integration/report_each_test.go b/internal/internal_integration/report_each_test.go index 1fff7cf3e..3456772e2 100644 --- a/internal/internal_integration/report_each_test.go +++ b/internal/internal_integration/report_each_test.go @@ -98,6 +98,17 @@ var _ = Describe("Sending reports to ReportBeforeEach and ReportAfterEach nodes" reports["interrupt"] = append(reports["interrupt"], report) }) }) + Context("when a after each reporter times out", func() { + It("passes", rt.T("passes")) + ReportAfterEach(func(ctx SpecContext, report types.SpecReport) { + select { + case <-ctx.Done(): + rt.Run("timeout-reporter") + reports["timeout"] = append(reports["timeout"], report) + case <-time.After(100 * time.Millisecond): + } + }, NodeTimeout(10*time.Millisecond)) + }) }) ReportBeforeEach(func(report types.SpecReport) { rt.Run("outer-RBE") @@ -114,15 +125,16 @@ var _ = Describe("Sending reports to ReportBeforeEach and ReportAfterEach nodes" "outer-RBE", "inner-RBE", "passes", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "fails", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "panics", "inner-RAE", "outer-RAE", - "outer-RBE", "inner-RBE", "inner-RAE", "outer-RAE", //pending test + "outer-RBE", "inner-RBE", "inner-RAE", "outer-RAE", // pending test "outer-RBE", "inner-RBE", "skipped", "inner-RAE", "outer-RAE", - "outer-RBE", "inner-RBE", "inner-RAE", "outer-RAE", //flag-skipped test + "outer-RBE", "inner-RBE", "inner-RAE", "outer-RAE", // flag-skipped test "outer-RBE", "inner-RBE", "also-passes", "failing-RAE", "inner-RAE", "outer-RAE", - "outer-RBE", "inner-RBE", "failing-in-skip-RAE", "inner-RAE", "outer-RAE", //is also flag-skipped + "outer-RBE", "inner-RBE", "failing-in-skip-RAE", "inner-RAE", "outer-RAE", // is also flag-skipped "outer-RBE", "inner-RBE", "writer", "writing-reporter", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "failing-RBE", "not-failing-RBE", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "passes-yet-again", "inner-RAE", "outer-RAE", - "outer-RBE", "inner-RBE", "interrupt-reporter", "inner-RAE", "outer-RAE", //skipped by interrupt + "outer-RBE", "inner-RBE", "interrupt-reporter", "inner-RAE", "outer-RAE", // skipped by interrupt + "outer-RBE", "inner-RBE", "timeout-reporter", "inner-RAE", "outer-RAE", // skipped by timeout "after-suite", )) }) @@ -191,7 +203,7 @@ var _ = Describe("Sending reports to ReportBeforeEach and ReportAfterEach nodes" Ω(reports["inner-RAE"].Find("writes stuff").CapturedGinkgoWriterOutput).Should(Equal("GinkgoWriter from It\nGinkgoWriter from ReportAfterEach\n")) Ω(reports["inner-RAE"].Find("writes stuff").CapturedStdOutErr).Should(Equal("Output from It\nOutput from ReportAfterEach\n")) - //but a report containing the additional output will be send to Ginkgo's reporter... + // but a report containing the additional output will be send to Ginkgo's reporter... Ω(reporter.Did.Find("writes stuff").CapturedGinkgoWriterOutput).Should((Equal("GinkgoWriter from It\nGinkgoWriter from ReportAfterEach\n"))) Ω(reporter.Did.Find("writes stuff").CapturedStdOutErr).Should((Equal("Output from It\nOutput from ReportAfterEach\n"))) }) diff --git a/internal/internal_integration/report_suite_test.go b/internal/internal_integration/report_suite_test.go index c2ff026ad..a724bd6a9 100644 --- a/internal/internal_integration/report_suite_test.go +++ b/internal/internal_integration/report_suite_test.go @@ -11,11 +11,12 @@ import ( ) var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite nodes", func() { - var failInReportBeforeSuiteA, failInReportAfterSuiteA, timeoutInReportAfterSuiteC, interruptSuiteB bool + var failInReportBeforeSuiteA, timeoutInReportBeforeSuiteB, failInReportAfterSuiteA, timeoutInReportAfterSuiteC, interruptSuiteB bool var fixture func() BeforeEach(func() { failInReportBeforeSuiteA = false + timeoutInReportBeforeSuiteB = false failInReportAfterSuiteA = false timeoutInReportAfterSuiteC = false interruptSuiteB = false @@ -32,11 +33,20 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node F("fail in report-before-suite-A") } }) - ReportBeforeSuite(func(report Report) { + ReportBeforeSuite(func(ctx SpecContext, report Report) { + timeout := 200 * time.Millisecond + if timeoutInReportBeforeSuiteB { + timeout = timeout + 1*time.Second + } rt.RunWithData("report-before-suite-B", "report", report) writer.Print("gw-report-before-suite-B") - outputInterceptor.AppendInterceptedOutput("out-report-before-suite-B") - }) + select { + case <-ctx.Done(): + outputInterceptor.AppendInterceptedOutput("timeout-report-before-suite-B") + case <-time.After(timeout): + outputInterceptor.AppendInterceptedOutput("out-report-before-suite-B") + } + }, NodeTimeout(500*time.Millisecond)) Context("container", func() { It("A", rt.T("A")) It("B", rt.T("B", func() { @@ -190,6 +200,24 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node }) }) + Context("when a ReportBeforeSuite times out", func() { + BeforeEach(func() { + timeoutInReportBeforeSuiteB = true + success, _ := RunFixture("report-before-suite-B-timed-out", fixture) + Ω(success).Should(BeFalse()) + }) + + It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { + Ω(reporter.Did.WithLeafNodeType(types.NodeTypeReportBeforeSuite).WithState(types.SpecStateTimedout)). + Should(ContainElement(HaveTimedOut( + types.NodeTypeReportBeforeSuite, + "A node timeout occurred", + CapturedGinkgoWriterOutput("gw-report-before-suite-B"), + CapturedStdOutput("timeout-report-before-suite-B"), + ))) + }) + }) + Context("when a ReportAfterSuite times out", func() { BeforeEach(func() { timeoutInReportAfterSuiteC = true diff --git a/internal/node.go b/internal/node.go index 5fe3b024f..6a15f19ae 100644 --- a/internal/node.go +++ b/internal/node.go @@ -15,8 +15,8 @@ var _global_node_id_counter = uint(0) var _global_id_mutex = &sync.Mutex{} func UniqueNodeID() uint { - //There's a reace in the internal integration tests if we don't make - //accessing _global_node_id_counter safe across goroutines. + // There's a reace in the internal integration tests if we don't make + // accessing _global_node_id_counter safe across goroutines. _global_id_mutex.Lock() defer _global_id_mutex.Unlock() _global_node_id_counter += 1 @@ -43,7 +43,7 @@ type Node struct { SynchronizedAfterSuiteProc1Body func(SpecContext) SynchronizedAfterSuiteProc1BodyHasContext bool - ReportEachBody func(types.SpecReport) + ReportEachBody func(SpecContext, types.SpecReport) ReportSuiteBody func(SpecContext, types.Report) MarkedFocus bool @@ -208,7 +208,7 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy args = unrollInterfaceSlice(args) remainingArgs := []interface{}{} - //First get the CodeLocation up-to-date + // First get the CodeLocation up-to-date for _, arg := range args { switch v := arg.(type) { case Offset: @@ -224,11 +224,11 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy trackedFunctionError := false args = remainingArgs remainingArgs = []interface{}{} - //now process the rest of the args + // now process the rest of the args for _, arg := range args { switch t := reflect.TypeOf(arg); { case t == reflect.TypeOf(float64(0)): - break //ignore deprecated timeouts + break // ignore deprecated timeouts case t == reflect.TypeOf(Focus): node.MarkedFocus = bool(arg.(focusType)) if !nodeType.Is(types.NodeTypesForContainerAndIt) { @@ -324,7 +324,12 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy node.Body = func(SpecContext) { body() } } else if nodeType.Is(types.NodeTypeReportBeforeEach | types.NodeTypeReportAfterEach) { if node.ReportEachBody == nil { - node.ReportEachBody = arg.(func(types.SpecReport)) + if fn, ok := arg.(func(types.SpecReport)); ok { + node.ReportEachBody = func(_ SpecContext, r types.SpecReport) { fn(r) } + } else { + node.ReportEachBody = arg.(func(SpecContext, types.SpecReport)) + node.HasContext = true + } } else { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true @@ -399,7 +404,7 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy } } - //validations + // validations if node.MarkedPending && node.MarkedFocus { appendError(types.GinkgoErrors.InvalidDeclarationOfFocusedAndPending(node.CodeLocation, nodeType)) } diff --git a/internal/node_test.go b/internal/node_test.go index 05641a65c..8d98e316f 100644 --- a/internal/node_test.go +++ b/internal/node_test.go @@ -142,7 +142,7 @@ var _ = Describe("Constructing nodes", func() { Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeReportBeforeEach)) - node.ReportEachBody(types.SpecReport{}) + node.ReportEachBody(internal.NewSpecContext(nil), types.SpecReport{}) Ω(didRun).Should(BeTrue()) Ω(node.Body).Should(BeNil()) @@ -162,7 +162,7 @@ var _ = Describe("Constructing nodes", func() { Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeReportAfterEach)) - node.ReportEachBody(types.SpecReport{}) + node.ReportEachBody(internal.NewSpecContext(nil), types.SpecReport{}) Ω(didRun).Should(BeTrue()) Ω(node.Body).Should(BeNil()) @@ -196,7 +196,7 @@ var _ = Describe("Constructing nodes", func() { cl := types.NewCodeLocation(2) cl2 := types.NewCustomCodeLocation("hi") node, errors := internal.NewNode(dt, ntIt, "text", body, cl2, Offset(1)) - //note that Offset overrides cl2 + // note that Offset overrides cl2 Ω(node.CodeLocation.FileName).Should(Equal(cl.FileName)) ExpectAllWell(errors) }) @@ -655,7 +655,7 @@ var _ = Describe("Constructing nodes", func() { }) var _ = Describe("Node", func() { - //HERE - and all the fun edge cases + // HERE - and all the fun edge cases Describe("The nodes that take more specific functions", func() { var dt *types.DeprecationTracker BeforeEach(func() { diff --git a/internal/suite.go b/internal/suite.go index 44b531ffd..a994ee3d6 100644 --- a/internal/suite.go +++ b/internal/suite.go @@ -594,8 +594,8 @@ func (suite *Suite) reportEach(spec Spec, nodeType types.NodeType) { suite.writer.Truncate() suite.outputInterceptor.StartInterceptingOutput() report := suite.currentSpecReport - nodes[i].Body = func(SpecContext) { - nodes[i].ReportEachBody(report) + nodes[i].Body = func(ctx SpecContext) { + nodes[i].ReportEachBody(ctx, report) } state, failure := suite.runNode(nodes[i], time.Time{}, spec.Nodes.BestTextFor(nodes[i])) @@ -840,7 +840,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ timeoutInPlay = "node" } if (!deadline.IsZero() && deadline.Before(now)) || interruptStatus.Interrupted() { - //we're out of time already. let's wait for a NodeTimeout if we have it, or GracePeriod if we don't + // we're out of time already. let's wait for a NodeTimeout if we have it, or GracePeriod if we don't if node.NodeTimeout > 0 { deadline = now.Add(node.NodeTimeout) timeoutInPlay = "node" @@ -918,9 +918,9 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ if outcomeFromRun != types.SpecStatePassed { additionalFailure := types.AdditionalFailure{ State: outcomeFromRun, - Failure: failure, //we make a copy - this will include all the configuration set up above... + Failure: failure, // we make a copy - this will include all the configuration set up above... } - //...and then we update the failure with the details from failureFromRun + // ...and then we update the failure with the details from failureFromRun additionalFailure.Failure.Location, additionalFailure.Failure.ForwardedPanic, additionalFailure.Failure.TimelineLocation = failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation additionalFailure.Failure.ProgressReport = types.ProgressReport{} if outcome == types.SpecStateTimedout { @@ -959,7 +959,7 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ // tell the spec to stop. it's important we generate the progress report first to make sure we capture where // the spec is actually stuck sc.cancel(fmt.Errorf("%s timeout occurred", timeoutInPlay)) - //and now we wait for the grace period + // and now we wait for the grace period gracePeriodChannel = time.After(gracePeriod) case <-interruptStatus.Channel: interruptStatus = suite.interruptHandler.Status() diff --git a/reporting_dsl.go b/reporting_dsl.go index 2eb79ea32..aa1a35176 100644 --- a/reporting_dsl.go +++ b/reporting_dsl.go @@ -74,12 +74,21 @@ func AddReportEntry(name string, args ...interface{}) { /* ReportBeforeEach nodes are run for each spec, even if the spec is skipped or pending. ReportBeforeEach nodes take a function that -receives a SpecReport. They are called before the spec starts. +receives a SpecReport or both SpecContext and Report for interruptible behavior. They are called before the spec starts. + +Example: + + ReportBeforeEach(func(report SpecReport) { // process report }) + ReportBeforeEach(func(ctx SpecContext, report SpecReport) { + // process report + }), NodeTimeout(1 * time.Minute)) You cannot nest any other Ginkgo nodes within a ReportBeforeEach node's closure. You can learn more about ReportBeforeEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically + +You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes */ -func ReportBeforeEach(body func(SpecReport), args ...interface{}) bool { +func ReportBeforeEach(body any, args ...any) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) @@ -87,13 +96,23 @@ func ReportBeforeEach(body func(SpecReport), args ...interface{}) bool { } /* -ReportAfterEach nodes are run for each spec, even if the spec is skipped or pending. ReportAfterEach nodes take a function that -receives a SpecReport. They are called after the spec has completed and receive the final report for the spec. +ReportAfterEach nodes are run for each spec, even if the spec is skipped or pending. +ReportAfterEach nodes take a function that receives a SpecReport or both SpecContext and Report for interruptible behavior. +They are called after the spec has completed and receive the final report for the spec. + +Example: + + ReportAfterEach(func(report SpecReport) { // process report }) + ReportAfterEach(func(ctx SpecContext, report SpecReport) { + // process report + }), NodeTimeout(1 * time.Minute)) You cannot nest any other Ginkgo nodes within a ReportAfterEach node's closure. You can learn more about ReportAfterEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically + +You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes */ -func ReportAfterEach(body func(SpecReport), args ...interface{}) bool { +func ReportAfterEach(body any, args ...any) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) @@ -101,7 +120,15 @@ func ReportAfterEach(body func(SpecReport), args ...interface{}) bool { } /* -ReportBeforeSuite nodes are run at the beginning of the suite. ReportBeforeSuite nodes take a function that receives a suite Report. +ReportBeforeSuite nodes are run at the beginning of the suite. ReportBeforeSuite nodes take a function +that can either receive Report or both SpecContext and Report for interruptible behavior. + +Example Usage: + + ReportBeforeSuite(func(r Report) { // process report }) + ReportBeforeSuite(func(ctx SpecContext, r Report) { + // process report + }, NodeTimeout(1 * time.Minute)) They are called at the beginning of the suite, before any specs have run and any BeforeSuite or SynchronizedBeforeSuite nodes, and are passed in the initial report for the suite. ReportBeforeSuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node) @@ -112,8 +139,10 @@ You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure. You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically You can learn more about Ginkgo's reporting infrastructure, including generating reports with the CLI here: https://onsi.github.io/ginkgo/#generating-machine-readable-reports + +You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes */ -func ReportBeforeSuite(body func(Report), args ...interface{}) bool { +func ReportBeforeSuite(body any, args ...any) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportBeforeSuite, "", combinedArgs...)) @@ -121,12 +150,14 @@ func ReportBeforeSuite(body func(Report), args ...interface{}) bool { /* ReportAfterSuite nodes are run at the end of the suite. ReportAfterSuite nodes execute at the suite's conclusion, -accepting a function that can either receives Report or both SpecContext and Report for interruptible behavior. +and accept a function that can either receive Report or both SpecContext and Report for interruptible behavior. Example Usage: - ReportAfterSuite("Non-interruptible ReportAfterSuite", func(r Report) { }) - ReportAfterSuite("Interruptible ReportAfterSuite", func(ctx SpecContext, r Report) { }) + ReportAfterSuite("Non-interruptible ReportAfterSuite", func(r Report) { // process report }) + ReportAfterSuite("Interruptible ReportAfterSuite", func(ctx SpecContext, r Report) { + // process report + }, NodeTimeout(1 * time.Minute)) They are called at the end of the suite, after all specs have run and any AfterSuite or SynchronizedAfterSuite nodes, and are passed in the final report for the suite. ReportAfterSuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node)