From 5ff9d7fafb94f34fa83fbbc29132d1ef60890e19 Mon Sep 17 00:00:00 2001 From: Eugene Nosenko Date: Sun, 25 Feb 2024 06:46:15 +0100 Subject: [PATCH] Add SpecContext to ReportAfterSuite callback body. --- .../internal_integration/report_suite_test.go | 46 ++++++++++++++++--- internal/node.go | 4 +- reporting_dsl.go | 20 ++++++-- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/internal/internal_integration/report_suite_test.go b/internal/internal_integration/report_suite_test.go index 0c09da03a..4702e7936 100644 --- a/internal/internal_integration/report_suite_test.go +++ b/internal/internal_integration/report_suite_test.go @@ -11,12 +11,13 @@ import ( ) var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite nodes", func() { - var failInReportBeforeSuiteA, failInReportAfterSuiteA, interruptSuiteB bool + var failInReportBeforeSuiteA, failInReportAfterSuiteA, timeoutInReportAfterSuiteC, interruptSuiteB bool var fixture func() BeforeEach(func() { failInReportBeforeSuiteA = false failInReportAfterSuiteA = false + timeoutInReportAfterSuiteC = false interruptSuiteB = false conf.RandomSeed = 17 fixture = func() { @@ -61,6 +62,19 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node writer.Print("gw-report-after-suite-B") outputInterceptor.AppendInterceptedOutput("out-report-after-suite-B") }) + ReportAfterSuite("Report C", func(ctx SpecContext, report Report) { + timeout := 200 * time.Millisecond + if timeoutInReportAfterSuiteC { + timeout = timeout + 1*time.Second + } + rt.RunWithData("report-after-suite-C", "report", report) + writer.Print("gw-report-after-suite-C") + outputInterceptor.AppendInterceptedOutput("out-report-after-suite-C") + select { + case <-ctx.Done(): + case <-time.After(timeout): + } + }, NodeTimeout(500*time.Millisecond)) AfterSuite(rt.T("after-suite", func() { writer.Print("gw-after-suite") F("fail in after-suite") @@ -87,7 +101,7 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node "before-suite", "A", "B", "C", "after-suite", - "report-after-suite-A", "report-after-suite-B", + "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) @@ -159,7 +173,7 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node It("doesn't run any specs - just reporting functions", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", - "report-after-suite-A", "report-after-suite-B", + "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) @@ -176,6 +190,23 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node }) }) + Context("when a ReportAfterSuiteContext times out", func() { + BeforeEach(func() { + timeoutInReportAfterSuiteC = true + success, _ := RunFixture("report-after-suite-C-timed-out", fixture) + Ω(success).Should(BeFalse()) + }) + + It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { + Ω(reporter.Did.Find("Report C")).Should(HaveTimedOut( + types.NodeTypeReportAfterSuite, + "A node timeout occurred", + CapturedGinkgoWriterOutput("gw-report-after-suite-C"), + CapturedStdOutput("out-report-after-suite-C"), + )) + }) + }) + Context("when a ReportAfterSuite node fails", func() { BeforeEach(func() { failInReportAfterSuiteA = true @@ -189,7 +220,7 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node "before-suite", "A", "B", "C", "after-suite", - "report-after-suite-A", "report-after-suite-B", + "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) @@ -220,6 +251,7 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node "A", "B", "C", "after-suite", "report-after-suite-A", + "report-after-suite-C", )) }) }) @@ -259,7 +291,7 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node "before-suite", "A", "B", "C", "after-suite", - "report-after-suite-A", "report-after-suite-B", + "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) @@ -309,7 +341,7 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node Context("when a non-primary proc disappears before it reports", func() { BeforeEach(func() { - close(exitChannels[2]) //proc 2 disappears before reporting + close(exitChannels[2]) // proc 2 disappears before reporting success, _ := RunFixture("disappearing-proc-2", fixture) Ω(success).Should(BeFalse()) }) @@ -342,7 +374,7 @@ var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite node It("only runs the reporting nodes", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", - "report-after-suite-A", "report-after-suite-B", + "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) diff --git a/internal/node.go b/internal/node.go index 6699b030a..5fe3b024f 100644 --- a/internal/node.go +++ b/internal/node.go @@ -5,9 +5,8 @@ import ( "fmt" "reflect" "sort" - "time" - "sync" + "time" "github.com/onsi/ginkgo/v2/types" ) @@ -337,6 +336,7 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy node.ReportSuiteBody = func(_ SpecContext, r types.Report) { fn(r) } } else { node.ReportSuiteBody = arg.(func(SpecContext, types.Report)) + node.HasContext = true } } else { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) diff --git a/reporting_dsl.go b/reporting_dsl.go index f33786a2d..3447c2ecf 100644 --- a/reporting_dsl.go +++ b/reporting_dsl.go @@ -120,10 +120,22 @@ func ReportBeforeSuite(body func(Report), args ...interface{}) bool { } /* -ReportAfterSuite nodes are run at the end of the suite. ReportAfterSuite nodes take a function that receives a suite Report. +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. + +Example Usage: + + ReportAfterSuite("Non-interruptible ReportAfterSuite", func(r Report) { }) + ReportAfterSuite("Interruptible ReportAfterSuite", func(ctx SpecContext, r Report) { }) + +These nodes must be placed at the top-level of your test suite, ensuring they're not nested within Context, Describe, or When nodes, to maintain clear, hierarchical test structures. + +In parallel test execution, Ginkgo ensures a singular ReportAfterSuite node runs, aggregating reports across all nodes for consistency. + +ReportAfterSuite supports generating detailed suite reports programmatically and via CLI flags (--json-report, --junit-report, and --teamcity-report) for various report formats. However, nesting other Ginkgo nodes within ReportAfterSuite's closure is not permitted. 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) +ReportAfterSuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node) When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportAfterSuite and that it is passed a report that is aggregated across all parallel nodes @@ -134,8 +146,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 ReportAfterSuite(text string, body func(Report), args ...interface{}) bool { +func ReportAfterSuite(text string, body any, args ...interface{}) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterSuite, text, combinedArgs...))