diff --git a/docs/index.md b/docs/index.md index fb17a7c97..57d46a1a7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3266,7 +3266,6 @@ ReportAfterEach(func(report SpecReport) { `ReportAfterEach` has several unique properties that distinguish it from `AfterEach`. Most importantly, `ReportAfterEach` closures are **always** called - even if the spec has failed, is marked pending, or is skipped. This ensures reports that rely on `ReportAfterEach` are complete. - In addition, `ReportAfterEach` closures are called after a spec completes. i.e. _after_ all `AfterEach` closures have run. This gives them access to the complete final state of the spec. Note that if a failure occurs in a `ReportAfterEach` your the spec will be marked as failed. Subsequent `ReportAfterEach` closures will see the failed state, but not the closure in which the failure occurred. `ReportAfterEach` is useful if you need to stream or emit up-to-date information about the suite as it runs. Ginkgo also provides `ReportBeforeEach` which is called before the test runs and receives a preliminary `types.SpecReport` - the state of this report will indicate whether the test will be skipped or is marked pending. @@ -3287,22 +3286,26 @@ ReportAfterEach(func(report SpecReport) { you'll end up with multiple processes writing to the same file and the output will be a mess. There is a better approach for this usecase... -#### Reporting Nodes - ReportAfterSuite -`ReportAfterSuite` nodes behave similarly to `AfterSuite` and can be placed at the top-level of your suite (typically in the suite bootstrap file). `ReportAfterSuite` nodes take a closure that accepts a single [`Report`]((https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#Report)) argument: +#### Reporting Nodes - ReportBeforeSuite and ReportAfterSuite +`ReportBeforeSuite` and `ReportAfterSuite` nodes behave similarly to `BeforeSuite` and `AfterSuite` and can be placed at the top-level of your suite (typically in the suite bootstrap file). `ReportBeforeSuite` and `ReportAfterSuite` nodes take a closure that accepts a single [`Report`]((https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#Report)) argument: ```go +var _ = ReportBeforeSuite(func(report Report) { + // process report +}) + var _ = ReportAfterSuite("custom report", func(report Report) { // process report }) ``` -`Report` contains all available information about the suite, including individual `SpecReport` entries for each spec that ran in the suite, and the overall status of the suite (whether it passed or failed). +`Report` contains all available information about the suite. For `ReportAfterSuite` this will include individual `SpecReport` entries for each spec that ran in the suite, and the overall status of the suite (whether it passed or failed). Since `ReportBeforeSuite` runs before the suite starts - it does not contain any spec reports, however the count of the number of specs that _will_ be run can be extracted from `report.PreRunStats.SpecsThatWillBeRun`. -The closure passed to `ReportAfterSuite` is called exactly once at the end of the suite after any `AfterSuite` nodes have run. Just like `ReportAfterEach`, `ReportAfterSuite` nodes can't be interrupted by the user to ensure the integrity of the generated report - so you'll want to make sure the code you put in there doesn't have a chance of hanging/getting stuck. +The closure passed to `ReportBeforeSuite` is called exactly once at the beginning of the suite before any `BeforeSuite` nodes or specs run have run. The closure passed to `ReportAfterSuite` is called exactly once at the end of the suite after any `AfterSuite` nodes have run. -Finally, and most importantly, when running in parallel `ReportAfterSuite` **only runs on process #1** and receives a `Report` that aggregates the `SpecReports` from all processes. This allows you to perform any custom suite reporting in one place after all specs have run and not have to worry about aggregating information across multiple parallel processes. +Finally, and most importantly, when running in parallel both `ReportBeforeSuite` and `ReportAfterSuite` **only run on process #1**. Gingko guarantess that no other processes will start running their specs until after `ReportBeforeSuite` on process #1 has completed. Similarly, Ginkgo will only run `ReportAfterSuite` on process #1 after all other processes have finished and exited. Ginkgo provides a sinle `Report` that aggregates the `SpecReports` from all processes. This allows you to perform any custom suite reporting in one place after all specs have run and not have to worry about aggregating information across multiple parallel processes. -So, we can rewrite our invalid `ReportAfterEach` example from above into a valid `ReportAfterSuite` example: +Givne all this, we can rewrite our invalid `ReportAfterEach` example from above into a valid `ReportAfterSuite` example: ```go ReportAfterSuite("custom report", func(report Report) { diff --git a/dsl/reporting/reporting_dsl.go b/dsl/reporting/reporting_dsl.go index 5bee29bdd..a9652f506 100644 --- a/dsl/reporting/reporting_dsl.go +++ b/dsl/reporting/reporting_dsl.go @@ -1,7 +1,7 @@ /* Ginkgo is usually dot-imported via: - import . "github.com/onsi/ginkgo/v2" + import . "github.com/onsi/ginkgo/v2" however some parts of the DSL may conflict with existing symbols in the user's code. @@ -27,4 +27,5 @@ var AddReportEntry = ginkgo.AddReportEntry var ReportBeforeEach = ginkgo.ReportBeforeEach var ReportAfterEach = ginkgo.ReportAfterEach +var ReportBeforeSuite = ginkgo.ReportBeforeSuite var ReportAfterSuite = ginkgo.ReportAfterSuite diff --git a/integration/_fixtures/reporting_fixture/reporting_fixture_suite_test.go b/integration/_fixtures/reporting_fixture/reporting_fixture_suite_test.go index 5ecd88f71..7a87753a7 100644 --- a/integration/_fixtures/reporting_fixture/reporting_fixture_suite_test.go +++ b/integration/_fixtures/reporting_fixture/reporting_fixture_suite_test.go @@ -17,6 +17,16 @@ func TestReportingFixture(t *testing.T) { var beforeEachReport, afterEachReport *os.File +var _ = ReportBeforeSuite(func(report Report) { + f, err := os.Create(fmt.Sprintf("report-before-suite-%d.out", GinkgoParallelProcess())) + Ω(err).ShouldNot(HaveOccurred()) + + fmt.Fprintf(f, "%s - %d\n", report.SuiteDescription, report.SuiteConfig.RandomSeed) + fmt.Fprintf(f, "%d of %d", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs) + + f.Close() +}) + var _ = BeforeSuite(func() { var err error beforeEachReport, err = os.Create("report-before-each.out") diff --git a/integration/reporting_test.go b/integration/reporting_test.go index ec4700820..c382d1733 100644 --- a/integration/reporting_test.go +++ b/integration/reporting_test.go @@ -19,7 +19,20 @@ var _ = Describe("Reporting", func() { fm.MountFixture("reporting") }) - Describe("in-suite reporting with ReportBeforeEach, ReportAfterEach and ReportAfterSuite", func() { + Describe("in-suite reporting with ReportBeforeEach, ReportAfterEach, ReportBeforeSuite and ReportAfterSuite", func() { + It("preview thes uite via ReportBeforeSuite", func() { + session := startGinkgo(fm.PathTo("reporting"), "--no-color", "--seed=17", "--procs=2") + Eventually(session).Should(gexec.Exit(1)) + + report, err := os.ReadFile(fm.PathTo("reporting", "report-before-suite-1.out")) + Ω(err).ShouldNot(HaveOccurred()) + lines := strings.Split(string(report), "\n") + Ω(lines).Should(ConsistOf( + "ReportingFixture Suite - 17", + "7 of 8", + )) + }) + It("reports on each test via ReportBeforeEach", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color") Eventually(session).Should(gexec.Exit(1)) @@ -69,6 +82,7 @@ var _ = Describe("Reporting", func() { lines := strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "ReportingFixture Suite - 17", + "1: [ReportBeforeSuite] - passed", "1: [BeforeSuite] - passed", "passes - passed", "is labelled - passed", @@ -85,15 +99,29 @@ var _ = Describe("Reporting", func() { }) Context("when running in parallel", func() { - It("reports on all the tests via ReportAfterSuite", func() { + It("reports only runs ReportBeforeSuite on proc 1 and reports on all the tests via ReportAfterSuite", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "--seed=17", "--procs=2") Eventually(session).Should(gexec.Exit(1)) - report, err := os.ReadFile(fm.PathTo("reporting", "report-after-suite.out")) + By("validating the ReportBeforeSuite report") + report, err := os.ReadFile(fm.PathTo("reporting", "report-before-suite-1.out")) Ω(err).ShouldNot(HaveOccurred()) lines := strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "ReportingFixture Suite - 17", + "7 of 8", + )) + + By("ensuring there is only one ReportBeforeSuite report") + Ω(fm.PathTo("reporting", "report-before-suite-2.out")).ShouldNot(BeARegularFile()) + + By("validating the ReportAfterSuite report") + report, err = os.ReadFile(fm.PathTo("reporting", "report-after-suite.out")) + Ω(err).ShouldNot(HaveOccurred()) + lines = strings.Split(string(report), "\n") + Ω(lines).Should(ConsistOf( + "ReportingFixture Suite - 17", + "1: [ReportBeforeSuite] - passed", "1: [BeforeSuite] - passed", "2: [BeforeSuite] - passed", "passes - passed", @@ -129,7 +157,7 @@ var _ = Describe("Reporting", func() { Ω(report.SuitePath).Should(Equal(fm.AbsPathTo("reporting"))) Ω(report.SuiteDescription).Should(Equal("ReportingFixture Suite")) Ω(report.SuiteConfig.ParallelTotal).Should(Equal(2)) - Ω(report.SpecReports).Should(HaveLen(15)) //8 tests + (1 before-suite + 2 defercleanup after-suite)*2(nodes) + 1 report-after-suite + Ω(report.SpecReports).Should(HaveLen(16)) //8 tests + (1 before-suite + 2 defercleanup after-suite)*2(nodes) + 1 report-before-suite + 1 report-after-suite specReports := Reports(report.SpecReports) Ω(specReports.WithLeafNodeType(types.NodeTypeIt)).Should(HaveLen(8)) @@ -143,6 +171,7 @@ var _ = Describe("Reporting", func() { Ω(specReports.Find("times out and fails during cleanup")).Should(HaveTimedOut("A node timeout occurred")) Ω(specReports.Find("times out and fails during cleanup").AdditionalFailures[0].Failure.Message).Should(Equal("double-whammy")) Ω(specReports.Find("times out and fails during cleanup").AdditionalFailures[0].Failure.FailureNodeType).Should(Equal(types.NodeTypeCleanupAfterEach)) + Ω(specReports.FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(HavePassed()) Ω(specReports.Find("my report")).Should(HaveFailed("fail!", types.FailureNodeIsLeafNode, types.NodeTypeReportAfterSuite)) Ω(specReports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed()) Ω(specReports.FindByLeafNodeType(types.NodeTypeCleanupAfterSuite)).Should(HavePassed()) @@ -194,7 +223,7 @@ var _ = Describe("Reporting", func() { checkJUnitReport := func(suite reporters.JUnitTestSuite) { Ω(suite.Name).Should(Equal("ReportingFixture Suite")) Ω(suite.Package).Should(Equal(fm.AbsPathTo("reporting"))) - Ω(suite.Tests).Should(Equal(15)) + Ω(suite.Tests).Should(Equal(16)) Ω(suite.Disabled).Should(Equal(1)) Ω(suite.Skipped).Should(Equal(1)) Ω(suite.Errors).Should(Equal(1)) @@ -202,6 +231,7 @@ var _ = Describe("Reporting", func() { Ω(suite.Properties.WithName("SuiteSucceeded")).Should(Equal("false")) Ω(suite.Properties.WithName("RandomSeed")).Should(Equal("17")) Ω(suite.Properties.WithName("ParallelTotal")).Should(Equal("2")) + Ω(getTestCase("[ReportBeforeSuite]", suite.TestCases).Status).Should(Equal("passed")) Ω(getTestCase("[BeforeSuite]", suite.TestCases).Status).Should(Equal("passed")) Ω(getTestCase("[It] reporting test passes", suite.TestCases).Classname).Should(Equal("ReportingFixture Suite")) Ω(getTestCase("[It] reporting test passes", suite.TestCases).Status).Should(Equal("passed")) @@ -259,7 +289,7 @@ var _ = Describe("Reporting", func() { checkUnifiedJUnitReport := func(report reporters.JUnitTestSuites) { Ω(report.TestSuites).Should(HaveLen(3)) - Ω(report.Tests).Should(Equal(18)) + Ω(report.Tests).Should(Equal(19)) Ω(report.Disabled).Should(Equal(2)) Ω(report.Errors).Should(Equal(2)) Ω(report.Failures).Should(Equal(4)) diff --git a/internal/internal_integration/config_dry_run_test.go b/internal/internal_integration/config_dry_run_test.go index 6019791cc..724f47b9b 100644 --- a/internal/internal_integration/config_dry_run_test.go +++ b/internal/internal_integration/config_dry_run_test.go @@ -12,6 +12,7 @@ var _ = Describe("when config.DryRun is enabled", func() { conf.SkipStrings = []string{"E"} RunFixture("dry run", func() { + ReportBeforeSuite(func(report Report) { rt.RunWithData("report-before-suite", "report", report) }) BeforeSuite(rt.T("before-suite")) BeforeEach(rt.T("bef")) ReportBeforeEach(func(_ SpecReport) { rt.Run("report-before-each") }) @@ -31,6 +32,7 @@ var _ = Describe("when config.DryRun is enabled", func() { It("does not run any tests but does invoke reporters", func() { Ω(rt).Should(HaveTracked( + "report-before-suite", //BeforeSuite "report-before-each", "report-after-each", //A "report-before-each", "report-after-each", //B "report-before-each", "report-after-each", //C @@ -40,6 +42,12 @@ var _ = Describe("when config.DryRun is enabled", func() { )) }) + It("correctly calculates the number of specs that will run", func() { + report := rt.DataFor("report-before-suite")["report"].(Report) + Ω(report.PreRunStats.SpecsThatWillRun).Should(Equal(3)) + Ω(report.PreRunStats.TotalSpecs).Should(Equal(5)) + }) + It("reports on the tests (both that they will run and that they did run) and honors skip state", func() { Ω(reporter.Will.Names()).Should(Equal([]string{"A", "B", "C", "D", "E"})) Ω(reporter.Will.Find("C")).Should(BePending()) diff --git a/internal/internal_integration/labels_test.go b/internal/internal_integration/labels_test.go index 33267b92c..f51a5591d 100644 --- a/internal/internal_integration/labels_test.go +++ b/internal/internal_integration/labels_test.go @@ -88,6 +88,9 @@ var _ = Describe("Labels", func() { BeforeEach(func() { conf.LabelFilter = "!TopLevelLabel" success, hPF := RunFixture("labelled tests", func() { + ReportBeforeSuite(func(r Report) { + rt.RunWithData("RBS", "report", r) + }) BeforeSuite(rt.T("before-suite")) Describe("outer container", func() { It("A", rt.T("A")) @@ -106,12 +109,18 @@ var _ = Describe("Labels", func() { }) It("doesn't run anything except for reporters", func() { - Ω(rt).Should(HaveTracked("RAE-A", "RAE-B", "RAS")) + Ω(rt).Should(HaveTracked("RBS", "RAE-A", "RAE-B", "RAS")) }) It("skip everything", func() { Ω(reporter.Did.Find("A")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) }) + + It("reports the correct number of specs to ReportBeforeSuite", func() { + report := rt.DataFor("RBS")["report"].(Report) + Ω(report.PreRunStats.SpecsThatWillRun).Should(Equal(0)) + Ω(report.PreRunStats.TotalSpecs).Should(Equal(2)) + }) }) }) diff --git a/internal/internal_integration/report_after_suite_test.go b/internal/internal_integration/report_after_suite_test.go deleted file mode 100644 index c4b214245..000000000 --- a/internal/internal_integration/report_after_suite_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package internal_integration_test - -import ( - "time" - - . "github.com/onsi/ginkgo/v2" - "github.com/onsi/ginkgo/v2/internal/interrupt_handler" - . "github.com/onsi/ginkgo/v2/internal/test_helpers" - "github.com/onsi/ginkgo/v2/types" - . "github.com/onsi/gomega" -) - -var _ = Describe("Sending reports to ReportAfterSuite procs", func() { - var failInReportAfterSuiteA, interruptSuiteB bool - var fixture func() - - BeforeEach(func() { - failInReportAfterSuiteA = false - interruptSuiteB = false - conf.RandomSeed = 17 - fixture = func() { - BeforeSuite(rt.T("before-suite", func() { - outputInterceptor.AppendInterceptedOutput("out-before-suite") - })) - Context("container", func() { - It("A", rt.T("A")) - It("B", rt.T("B", func() { - F("fail in B") - })) - It("C", rt.T("C")) - PIt("D", rt.T("D")) - }) - ReportAfterSuite("Report A", func(report Report) { - rt.RunWithData("report-A", "report", report) - writer.Print("gw-report-A") - outputInterceptor.AppendInterceptedOutput("out-report-A") - if failInReportAfterSuiteA { - F("fail in report-A") - } - }) - ReportAfterSuite("Report B", func(report Report) { - if interruptSuiteB { - interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) - time.Sleep(time.Hour) - } - rt.RunWithData("report-B", "report", report) - writer.Print("gw-report-B") - outputInterceptor.AppendInterceptedOutput("out-report-B") - }) - AfterSuite(rt.T("after-suite", func() { - writer.Print("gw-after-suite") - F("fail in after-suite") - })) - - } - }) - - Context("when running in series", func() { - BeforeEach(func() { - conf.ParallelTotal = 1 - conf.ParallelProcess = 1 - }) - - Context("the happy path", func() { - BeforeEach(func() { - success, _ := RunFixture("happy-path", fixture) - Ω(success).Should(BeFalse()) - }) - - It("runs all the functions", func() { - Ω(rt).Should(HaveTracked( - "before-suite", - "A", "B", "C", - "after-suite", - "report-A", "report-B", - )) - }) - - It("reports on the report procs", func() { - Ω(reporter.Did.Find("Report A")).Should(HavePassed( - types.NodeTypeReportAfterSuite, - CapturedGinkgoWriterOutput("gw-report-A"), - CapturedStdOutput("out-report-A"), - )) - - Ω(reporter.Did.Find("Report B")).Should(HavePassed( - types.NodeTypeReportAfterSuite, - CapturedGinkgoWriterOutput("gw-report-B"), - CapturedStdOutput("out-report-B"), - )) - }) - - It("passes the report in to each reporter", func() { - reportA := rt.DataFor("report-A")["report"].(types.Report) - reportB := rt.DataFor("report-B")["report"].(types.Report) - - for _, report := range []types.Report{reportA, reportB} { - Ω(report.SuiteDescription).Should(Equal("happy-path")) - Ω(report.SuiteSucceeded).Should(BeFalse()) - Ω(report.SuiteConfig.RandomSeed).Should(Equal(int64(17))) - reports := Reports(report.SpecReports) - Ω(reports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed(CapturedStdOutput("out-before-suite"))) - Ω(reports.Find("A")).Should(HavePassed()) - Ω(reports.Find("B")).Should(HaveFailed("fail in B")) - Ω(reports.Find("C")).Should(HavePassed()) - Ω(reports.Find("D")).Should(BePending()) - Ω(reports.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveFailed("fail in after-suite", CapturedGinkgoWriterOutput("gw-after-suite"))) - } - - Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") - Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) - }) - }) - - Context("when a ReportAfterSuite proc fails", func() { - BeforeEach(func() { - failInReportAfterSuiteA = true - success, _ := RunFixture("report-A-fails", fixture) - Ω(success).Should(BeFalse()) - }) - - It("keeps running subseuqent reporting functions", func() { - Ω(rt).Should(HaveTracked( - "before-suite", - "A", "B", "C", - "after-suite", - "report-A", "report-B", - )) - }) - - It("reports on the faitlure, to Ginkgo's reporter and any subsequent reporters", func() { - Ω(reporter.Did.Find("Report A")).Should(HaveFailed( - types.NodeTypeReportAfterSuite, - "fail in report-A", - CapturedGinkgoWriterOutput("gw-report-A"), - CapturedStdOutput("out-report-A"), - )) - - reportB := rt.DataFor("report-B")["report"].(types.Report) - Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) - }) - }) - - Context("when an interrupt is attempted in a ReportAfterSuiteNode", func() { - BeforeEach(func() { - interruptSuiteB = true - success, _ := RunFixture("report-B-interrupted", fixture) - Ω(success).Should(BeFalse()) - }) - - It("interrupts and bails", func() { - Ω(rt).Should(HaveTracked( - "before-suite", - "A", "B", "C", - "after-suite", - "report-A", - )) - }) - }) - }) - - Context("when running in parallel", func() { - var otherNodeReport types.Report - - BeforeEach(func() { - SetUpForParallel(2) - - otherNodeReport = types.Report{ - SpecReports: types.SpecReports{ - types.SpecReport{LeafNodeText: "E", LeafNodeLocation: cl, State: types.SpecStatePassed, LeafNodeType: types.NodeTypeIt}, - types.SpecReport{LeafNodeText: "F", LeafNodeLocation: cl, State: types.SpecStateSkipped, LeafNodeType: types.NodeTypeIt}, - }, - } - }) - - Context("on proc 1", func() { - BeforeEach(func() { - conf.ParallelProcess = 1 - }) - - Context("the happy path", func() { - BeforeEach(func() { - // proc 2 has reported back and exited - client.PostSuiteDidEnd(otherNodeReport) - close(exitChannels[2]) - success, _ := RunFixture("happy-path", fixture) - Ω(success).Should(BeFalse()) - }) - - It("runs all the functions", func() { - Ω(rt).Should(HaveTracked( - "before-suite", - "A", "B", "C", - "after-suite", - "report-A", "report-B", - )) - }) - - It("passes the report in to each reporter, including information from other procs", func() { - reportA := rt.DataFor("report-A")["report"].(types.Report) - reportB := rt.DataFor("report-B")["report"].(types.Report) - - for _, report := range []types.Report{reportA, reportB} { - Ω(report.SuiteDescription).Should(Equal("happy-path")) - Ω(report.SuiteSucceeded).Should(BeFalse()) - reports := Reports(report.SpecReports) - Ω(reports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed(CapturedStdOutput("out-before-suite"))) - Ω(reports.Find("A")).Should(HavePassed()) - Ω(reports.Find("B")).Should(HaveFailed("fail in B")) - Ω(reports.Find("C")).Should(HavePassed()) - Ω(reports.Find("D")).Should(BePending()) - Ω(reports.Find("E")).Should(HavePassed()) - Ω(reports.Find("F")).Should(HaveBeenSkipped()) - Ω(reports.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveFailed("fail in after-suite", CapturedGinkgoWriterOutput("gw-after-suite"))) - } - - Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") - Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) - }) - }) - - Describe("waiting for reports from other procs", func() { - It("blocks until the other procs have finished", func() { - done := make(chan interface{}) - go func() { - defer GinkgoRecover() - success, _ := RunFixture("happy-path", fixture) - Ω(success).Should(BeFalse()) - close(done) - }() - Consistently(done).ShouldNot(BeClosed()) - client.PostSuiteDidEnd(otherNodeReport) - Consistently(done).ShouldNot(BeClosed()) - close(exitChannels[2]) - Eventually(done).Should(BeClosed()) - }) - }) - - Context("when a non-primary proc disappears before it reports", func() { - BeforeEach(func() { - close(exitChannels[2]) //proc 2 disappears before reporting - success, _ := RunFixture("disappearing-proc-2", fixture) - Ω(success).Should(BeFalse()) - }) - - It("does not run the ReportAfterSuite procs", func() { - Ω(rt).Should(HaveTracked( - "before-suite", - "A", "B", "C", - "after-suite", - )) - }) - - It("reports all the ReportAfterSuite procs as failed", func() { - Ω(reporter.Did.Find("Report A")).Should(HaveFailed(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing().Error())) - Ω(reporter.Did.Find("Report B")).Should(HaveFailed(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing().Error())) - }) - }) - }) - - Context("on a non-primary proc", func() { - BeforeEach(func() { - conf.ParallelProcess = 2 - success, _ := RunFixture("happy-path", fixture) - Ω(success).Should(BeFalse()) - }) - - It("does not run the ReportAfterSuite procs", func() { - Ω(rt).Should(HaveTracked( - "before-suite", - "A", "B", "C", - "after-suite", - )) - }) - }) - }) -}) diff --git a/internal/internal_integration/report_suite_test.go b/internal/internal_integration/report_suite_test.go new file mode 100644 index 000000000..74e82e3fe --- /dev/null +++ b/internal/internal_integration/report_suite_test.go @@ -0,0 +1,411 @@ +package internal_integration_test + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/internal/interrupt_handler" + . "github.com/onsi/ginkgo/v2/internal/test_helpers" + "github.com/onsi/ginkgo/v2/types" + . "github.com/onsi/gomega" +) + +var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite nodes", func() { + var failInReportBeforeSuiteA, failInReportAfterSuiteA, interruptSuiteB bool + var fixture func() + + BeforeEach(func() { + failInReportBeforeSuiteA = false + failInReportAfterSuiteA = false + interruptSuiteB = false + conf.RandomSeed = 17 + fixture = func() { + BeforeSuite(rt.T("before-suite", func() { + outputInterceptor.AppendInterceptedOutput("out-before-suite") + })) + ReportBeforeSuite(func(report Report) { + rt.RunWithData("report-before-suite-A", "report", report) + writer.Print("gw-report-before-suite-A") + outputInterceptor.AppendInterceptedOutput("out-report-before-suite-A") + if failInReportBeforeSuiteA { + F("fail in report-before-suite-A") + } + }) + ReportBeforeSuite(func(report Report) { + rt.RunWithData("report-before-suite-B", "report", report) + writer.Print("gw-report-before-suite-B") + outputInterceptor.AppendInterceptedOutput("out-report-before-suite-B") + }) + Context("container", func() { + It("A", rt.T("A")) + It("B", rt.T("B", func() { + F("fail in B") + })) + It("C", rt.T("C")) + PIt("D", rt.T("D")) + }) + ReportAfterSuite("Report A", func(report Report) { + rt.RunWithData("report-after-suite-A", "report", report) + writer.Print("gw-report-after-suite-A") + outputInterceptor.AppendInterceptedOutput("out-report-after-suite-A") + if failInReportAfterSuiteA { + F("fail in report-after-suite-A") + } + }) + ReportAfterSuite("Report B", func(report Report) { + if interruptSuiteB { + interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) + time.Sleep(time.Hour) + } + rt.RunWithData("report-after-suite-B", "report", report) + writer.Print("gw-report-after-suite-B") + outputInterceptor.AppendInterceptedOutput("out-report-after-suite-B") + }) + AfterSuite(rt.T("after-suite", func() { + writer.Print("gw-after-suite") + F("fail in after-suite") + })) + + } + }) + + Context("when running in series", func() { + BeforeEach(func() { + conf.ParallelTotal = 1 + conf.ParallelProcess = 1 + }) + + Context("the happy path", func() { + BeforeEach(func() { + success, _ := RunFixture("happy-path", fixture) + Ω(success).Should(BeFalse()) + }) + + It("runs all the functions", func() { + Ω(rt).Should(HaveTracked( + "report-before-suite-A", "report-before-suite-B", + "before-suite", + "A", "B", "C", + "after-suite", + "report-after-suite-A", "report-after-suite-B", + )) + }) + + It("reports on the report procs", func() { + Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(HavePassed( + types.NodeTypeReportBeforeSuite, + CapturedGinkgoWriterOutput("gw-report-before-suite-A"), + CapturedStdOutput("out-report-before-suite-A"), + )) + + Ω(reporter.Did.Find("Report A")).Should(HavePassed( + types.NodeTypeReportAfterSuite, + CapturedGinkgoWriterOutput("gw-report-after-suite-A"), + CapturedStdOutput("out-report-after-suite-A"), + )) + + Ω(reporter.Did.Find("Report B")).Should(HavePassed( + types.NodeTypeReportAfterSuite, + CapturedGinkgoWriterOutput("gw-report-after-suite-B"), + CapturedStdOutput("out-report-after-suite-B"), + )) + }) + + It("passes the report in to each ReportBeforeSuite", func() { + reportA := rt.DataFor("report-before-suite-A")["report"].(types.Report) + reportB := rt.DataFor("report-before-suite-B")["report"].(types.Report) + + for _, report := range []types.Report{reportA, reportB} { + Ω(report.SuiteDescription).Should(Equal("happy-path")) + Ω(report.SuiteSucceeded).Should(BeTrue()) + Ω(report.SuiteConfig.RandomSeed).Should(Equal(int64(17))) + Ω(report.PreRunStats.SpecsThatWillRun).Should(Equal(3)) + Ω(report.PreRunStats.TotalSpecs).Should(Equal(4)) + } + + Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") + Ω(Reports(reportB.SpecReports).FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(Equal(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite))) + }) + + It("passes the report in to each ReportAfterSuite", func() { + reportA := rt.DataFor("report-after-suite-A")["report"].(types.Report) + reportB := rt.DataFor("report-after-suite-B")["report"].(types.Report) + + for _, report := range []types.Report{reportA, reportB} { + Ω(report.SuiteDescription).Should(Equal("happy-path")) + Ω(report.SuiteSucceeded).Should(BeFalse()) + Ω(report.SuiteConfig.RandomSeed).Should(Equal(int64(17))) + reports := Reports(report.SpecReports) + Ω(reports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed(CapturedStdOutput("out-before-suite"))) + Ω(reports.Find("A")).Should(HavePassed()) + Ω(reports.Find("B")).Should(HaveFailed("fail in B")) + Ω(reports.Find("C")).Should(HavePassed()) + Ω(reports.Find("D")).Should(BePending()) + Ω(reports.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveFailed("fail in after-suite", CapturedGinkgoWriterOutput("gw-after-suite"))) + } + + Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") + Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) + }) + }) + + Context("when a ReportBeforeSuite node fails", func() { + BeforeEach(func() { + failInReportBeforeSuiteA = true + success, _ := RunFixture("report-before-suite-A-fails", fixture) + Ω(success).Should(BeFalse()) + }) + + 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", + )) + }) + + It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { + Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(HaveFailed( + types.NodeTypeReportBeforeSuite, + "fail in report-before-suite-A", + CapturedGinkgoWriterOutput("gw-report-before-suite-A"), + CapturedStdOutput("out-report-before-suite-A"), + )) + + reportB := rt.DataFor("report-before-suite-B")["report"].(types.Report) + Ω(Reports(reportB.SpecReports).FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(Equal(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite))) + }) + }) + + Context("when a ReportAfterSuite node fails", func() { + BeforeEach(func() { + failInReportAfterSuiteA = true + success, _ := RunFixture("report-after-suite-A-fails", fixture) + Ω(success).Should(BeFalse()) + }) + + It("keeps running subseuqent reporting functions", func() { + Ω(rt).Should(HaveTracked( + "report-before-suite-A", "report-before-suite-B", + "before-suite", + "A", "B", "C", + "after-suite", + "report-after-suite-A", "report-after-suite-B", + )) + }) + + It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { + Ω(reporter.Did.Find("Report A")).Should(HaveFailed( + types.NodeTypeReportAfterSuite, + "fail in report-after-suite-A", + CapturedGinkgoWriterOutput("gw-report-after-suite-A"), + CapturedStdOutput("out-report-after-suite-A"), + )) + + reportB := rt.DataFor("report-after-suite-B")["report"].(types.Report) + Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) + }) + }) + + Context("when an interrupt is attempted in a ReportAfterSuiteNode", func() { + BeforeEach(func() { + interruptSuiteB = true + success, _ := RunFixture("report-after-suite-B-interrupted", fixture) + Ω(success).Should(BeFalse()) + }) + + It("interrupts and bails", func() { + Ω(rt).Should(HaveTracked( + "report-before-suite-A", "report-before-suite-B", + "before-suite", + "A", "B", "C", + "after-suite", + "report-after-suite-A", + )) + }) + }) + }) + + Context("when running in parallel", func() { + var otherNodeReport types.Report + + BeforeEach(func() { + SetUpForParallel(2) + + otherNodeReport = types.Report{ + SpecReports: types.SpecReports{ + types.SpecReport{LeafNodeText: "E", LeafNodeLocation: cl, State: types.SpecStatePassed, LeafNodeType: types.NodeTypeIt}, + types.SpecReport{LeafNodeText: "F", LeafNodeLocation: cl, State: types.SpecStateSkipped, LeafNodeType: types.NodeTypeIt}, + }, + } + }) + + Context("on proc 1", func() { + BeforeEach(func() { + conf.ParallelProcess = 1 + }) + + Context("the happy path", func() { + BeforeEach(func() { + // proc 2 has reported back and exited + client.PostSuiteDidEnd(otherNodeReport) + close(exitChannels[2]) + success, _ := RunFixture("happy-path", fixture) + Ω(success).Should(BeFalse()) + }) + + It("runs all the functions", func() { + Ω(rt).Should(HaveTracked( + "report-before-suite-A", "report-before-suite-B", + "before-suite", + "A", "B", "C", + "after-suite", + "report-after-suite-A", "report-after-suite-B", + )) + }) + + It("passes the report in to each reporter, including information from other procs", func() { + reportA := rt.DataFor("report-after-suite-A")["report"].(types.Report) + reportB := rt.DataFor("report-after-suite-B")["report"].(types.Report) + + for _, report := range []types.Report{reportA, reportB} { + Ω(report.SuiteDescription).Should(Equal("happy-path")) + Ω(report.SuiteSucceeded).Should(BeFalse()) + reports := Reports(report.SpecReports) + Ω(reports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed(CapturedStdOutput("out-before-suite"))) + Ω(reports.Find("A")).Should(HavePassed()) + Ω(reports.Find("B")).Should(HaveFailed("fail in B")) + Ω(reports.Find("C")).Should(HavePassed()) + Ω(reports.Find("D")).Should(BePending()) + Ω(reports.Find("E")).Should(HavePassed()) + Ω(reports.Find("F")).Should(HaveBeenSkipped()) + Ω(reports.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveFailed("fail in after-suite", CapturedGinkgoWriterOutput("gw-after-suite"))) + } + + Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") + Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) + }) + + It("tells the other procs that the ReportBeforeSuite has completed", func() { + Ω(client.BlockUntilReportBeforeSuiteCompleted()).Should(Equal(types.SpecStatePassed)) + }) + }) + + Describe("waiting for reports from other procs", func() { + It("blocks until the other procs have finished", func() { + done := make(chan interface{}) + go func() { + defer GinkgoRecover() + success, _ := RunFixture("happy-path", fixture) + Ω(success).Should(BeFalse()) + close(done) + }() + Consistently(done).ShouldNot(BeClosed()) + client.PostSuiteDidEnd(otherNodeReport) + Consistently(done).ShouldNot(BeClosed()) + close(exitChannels[2]) + Eventually(done).Should(BeClosed()) + }) + }) + + Context("when a non-primary proc disappears before it reports", func() { + BeforeEach(func() { + close(exitChannels[2]) //proc 2 disappears before reporting + success, _ := RunFixture("disappearing-proc-2", fixture) + Ω(success).Should(BeFalse()) + }) + + It("does not run the ReportAfterSuite procs", func() { + Ω(rt).Should(HaveTracked( + "report-before-suite-A", "report-before-suite-B", + "before-suite", + "A", "B", "C", + "after-suite", + )) + }) + + It("reports all the ReportAfterSuite procs as failed", func() { + Ω(reporter.Did.Find("Report A")).Should(HaveFailed(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing().Error())) + Ω(reporter.Did.Find("Report B")).Should(HaveFailed(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing().Error())) + }) + }) + + Context("when a ReportBeforeSuite fails", func() { + BeforeEach(func() { + // proc 2 has reported back and exited + client.PostSuiteDidEnd(otherNodeReport) + close(exitChannels[2]) + failInReportBeforeSuiteA = true + success, _ := RunFixture("failure-in-report-before-suite-A", fixture) + Ω(success).Should(BeFalse()) + }) + + 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", + )) + }) + + It("tells the other procs that the ReportBeforeSuite failed", func() { + Ω(client.BlockUntilReportBeforeSuiteCompleted()).Should(Equal(types.SpecStateFailed)) + }) + }) + }) + + Context("on a non-primary proc", func() { + var done chan interface{} + BeforeEach(func() { + done = make(chan interface{}) + go func() { + conf.ParallelProcess = 2 + success, _ := RunFixture("non-primary proc", fixture) + Ω(success).Should(BeFalse()) + close(done) + }() + Consistently(done).ShouldNot(BeClosed()) + + Ω(rt).Should(HaveTrackedNothing(), "Nothing should run until we are cleared to go by proc1") + }) + + Context("the happy path", func() { + BeforeEach(func() { + By("proc1 signals that its ReportBeforeSuites succeeded") + client.PostReportBeforeSuiteCompleted(types.SpecStatePassed) + Eventually(done).Should(BeClosed()) + }) + + It("does not run anything until the primary node finishes running the BeforeSuite node, then it runs the specs", func() { + Ω(rt).Should(HaveTracked( + "before-suite", + "A", "B", "C", + "after-suite", + )) + }) + }) + + Context("when the ReportBeforeSuite node fails", func() { + BeforeEach(func() { + By("proc1 signals that its ReportBeforeSuites failed") + client.PostReportBeforeSuiteCompleted(types.SpecStateFailed) + Eventually(done).Should(BeClosed()) + }) + + It("does not run anything until it is notified of the failure, then it just exits without running anything", func() { + Ω(rt).Should(HaveTrackedNothing()) + }) + }) + + Context("when proc1 exits before reporting", func() { + BeforeEach(func() { + By("proc1 signals that its ReportBeforeSuites failed") + close(exitChannels[1]) + Eventually(done).Should(BeClosed()) + }) + + It("does not run anything until it is notified of the failure, then it just exits without running anything", func() { + Ω(rt).Should(HaveTrackedNothing()) + }) + }) + }) + }) +}) diff --git a/internal/node.go b/internal/node.go index 0878c6728..69928eed6 100644 --- a/internal/node.go +++ b/internal/node.go @@ -44,8 +44,8 @@ type Node struct { SynchronizedAfterSuiteProc1Body func(SpecContext) SynchronizedAfterSuiteProc1BodyHasContext bool - ReportEachBody func(types.SpecReport) - ReportAfterSuiteBody func(types.Report) + ReportEachBody func(types.SpecReport) + ReportSuiteBody func(types.Report) MarkedFocus bool MarkedPending bool @@ -317,9 +317,9 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy trackedFunctionError = true break } - } else if nodeType.Is(types.NodeTypeReportAfterSuite) { - if node.ReportAfterSuiteBody == nil { - node.ReportAfterSuiteBody = arg.(func(types.Report)) + } else if nodeType.Is(types.NodeTypeReportBeforeSuite | types.NodeTypeReportAfterSuite) { + if node.ReportSuiteBody == nil { + node.ReportSuiteBody = arg.(func(types.Report)) } else { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true @@ -392,7 +392,7 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy appendError(types.GinkgoErrors.InvalidTimeoutOrGracePeriodForNonContextNode(node.CodeLocation, nodeType)) } - if !node.NodeType.Is(types.NodeTypeReportBeforeEach|types.NodeTypeReportAfterEach|types.NodeTypeSynchronizedBeforeSuite|types.NodeTypeSynchronizedAfterSuite|types.NodeTypeReportAfterSuite) && node.Body == nil && !node.MarkedPending && !trackedFunctionError { + if !node.NodeType.Is(types.NodeTypeReportBeforeEach|types.NodeTypeReportAfterEach|types.NodeTypeSynchronizedBeforeSuite|types.NodeTypeSynchronizedAfterSuite|types.NodeTypeReportBeforeSuite|types.NodeTypeReportAfterSuite) && node.Body == nil && !node.MarkedPending && !trackedFunctionError { appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) } diff --git a/internal/node_test.go b/internal/node_test.go index 680be78d0..aca3b13df 100644 --- a/internal/node_test.go +++ b/internal/node_test.go @@ -834,7 +834,7 @@ var _ = Describe("Node", func() { Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeReportAfterSuite)) - node.ReportAfterSuiteBody(types.Report{}) + node.ReportSuiteBody(types.Report{}) Ω(didRun).Should(BeTrue()) Ω(node.CodeLocation).Should(Equal(cl)) @@ -848,6 +848,30 @@ var _ = Describe("Node", func() { }) }) + Describe("ReportBeforeSuiteNode", func() { + It("returns a correctly configured node", func() { + var didRun bool + body := func(types.Report) { didRun = true } + node, errors := internal.NewNode(dt, types.NodeTypeReportBeforeSuite, "", body, cl) + Ω(errors).Should(BeEmpty()) + Ω(node.Text).Should(BeEmpty()) + Ω(node.ID).Should(BeNumerically(">", 0)) + Ω(node.NodeType).Should(Equal(types.NodeTypeReportBeforeSuite)) + + node.ReportSuiteBody(types.Report{}) + Ω(didRun).Should(BeTrue()) + + Ω(node.CodeLocation).Should(Equal(cl)) + Ω(node.NestingLevel).Should(Equal(-1)) + }) + + It("errors if passed too many functions", func() { + node, errors := internal.NewNode(dt, types.NodeTypeReportBeforeSuite, "", func(types.Report) {}, func() {}, cl) + Ω(node).Should(BeZero()) + Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, types.NodeTypeReportBeforeSuite))) + }) + }) + Describe("NewCleanupNode", func() { var capturedFailure string var capturedCL types.CodeLocation diff --git a/internal/parallel_support/client_server.go b/internal/parallel_support/client_server.go index b417bf5b3..b3cd64292 100644 --- a/internal/parallel_support/client_server.go +++ b/internal/parallel_support/client_server.go @@ -42,6 +42,8 @@ type Client interface { PostSuiteWillBegin(report types.Report) error PostDidRun(report types.SpecReport) error PostSuiteDidEnd(report types.Report) error + PostReportBeforeSuiteCompleted(state types.SpecState) error + BlockUntilReportBeforeSuiteCompleted() (types.SpecState, error) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error BlockUntilSynchronizedBeforeSuiteData() (types.SpecState, []byte, error) BlockUntilNonprimaryProcsHaveFinished() error diff --git a/internal/parallel_support/client_server_test.go b/internal/parallel_support/client_server_test.go index 848c8bee2..4c91c43ee 100644 --- a/internal/parallel_support/client_server_test.go +++ b/internal/parallel_support/client_server_test.go @@ -234,6 +234,51 @@ var _ = Describe("The Parallel Support Client & Server", func() { server.RegisterAlive(3, aliveFunc(proc3Exited)) }) + Describe("Managing ReportBeforeSuite synchronization", func() { + Context("when proc 1 succeeds", func() { + It("passes that success along to other procs", func() { + Ω(client.PostReportBeforeSuiteCompleted(types.SpecStatePassed)).Should(Succeed()) + state, err := client.BlockUntilReportBeforeSuiteCompleted() + Ω(state).Should(Equal(types.SpecStatePassed)) + Ω(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("when proc 1 fails", func() { + It("passes that state information along to the other procs", func() { + Ω(client.PostReportBeforeSuiteCompleted(types.SpecStateFailed)).Should(Succeed()) + state, err := client.BlockUntilReportBeforeSuiteCompleted() + Ω(state).Should(Equal(types.SpecStateFailed)) + Ω(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("when proc 1 disappears before reporting back", func() { + It("returns a meaningful error", func() { + close(proc1Exited) + state, err := client.BlockUntilReportBeforeSuiteCompleted() + Ω(state).Should(Equal(types.SpecStateFailed)) + Ω(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("when proc 1 hasn't responded yet", func() { + It("blocks until it does", func() { + done := make(chan interface{}) + go func() { + defer GinkgoRecover() + state, err := client.BlockUntilReportBeforeSuiteCompleted() + Ω(state).Should(Equal(types.SpecStatePassed)) + Ω(err).ShouldNot(HaveOccurred()) + close(done) + }() + Consistently(done).ShouldNot(BeClosed()) + Ω(client.PostReportBeforeSuiteCompleted(types.SpecStatePassed)).Should(Succeed()) + Eventually(done).Should(BeClosed()) + }) + }) + }) + Describe("Managing SynchronizedBeforeSuite synchronization", func() { Context("when proc 1 succeeds and returns data", func() { It("passes that data along to other procs", func() { diff --git a/internal/parallel_support/http_client.go b/internal/parallel_support/http_client.go index ad9932f2a..6547c7a66 100644 --- a/internal/parallel_support/http_client.go +++ b/internal/parallel_support/http_client.go @@ -98,6 +98,19 @@ func (client *httpClient) PostEmitProgressReport(report types.ProgressReport) er return client.post("/progress-report", report) } +func (client *httpClient) PostReportBeforeSuiteCompleted(state types.SpecState) error { + return client.post("/report-before-suite-completed", state) +} + +func (client *httpClient) BlockUntilReportBeforeSuiteCompleted() (types.SpecState, error) { + var state types.SpecState + err := client.poll("/report-before-suite-state", &state) + if err == ErrorGone { + return types.SpecStateFailed, nil + } + return state, err +} + func (client *httpClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { beforeSuiteState := BeforeSuiteState{ State: state, diff --git a/internal/parallel_support/http_server.go b/internal/parallel_support/http_server.go index fa3ac682a..d2c71ab1b 100644 --- a/internal/parallel_support/http_server.go +++ b/internal/parallel_support/http_server.go @@ -26,7 +26,7 @@ type httpServer struct { handler *ServerHandler } -//Create a new server, automatically selecting a port +// Create a new server, automatically selecting a port func newHttpServer(parallelTotal int, reporter reporters.Reporter) (*httpServer, error) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { @@ -38,7 +38,7 @@ func newHttpServer(parallelTotal int, reporter reporters.Reporter) (*httpServer, }, nil } -//Start the server. You don't need to `go s.Start()`, just `s.Start()` +// Start the server. You don't need to `go s.Start()`, just `s.Start()` func (server *httpServer) Start() { httpServer := &http.Server{} mux := http.NewServeMux() @@ -52,6 +52,8 @@ func (server *httpServer) Start() { mux.HandleFunc("/progress-report", server.emitProgressReport) //synchronization endpoints + mux.HandleFunc("/report-before-suite-completed", server.handleReportBeforeSuiteCompleted) + mux.HandleFunc("/report-before-suite-state", server.handleReportBeforeSuiteState) mux.HandleFunc("/before-suite-completed", server.handleBeforeSuiteCompleted) mux.HandleFunc("/before-suite-state", server.handleBeforeSuiteState) mux.HandleFunc("/have-nonprimary-procs-finished", server.handleHaveNonprimaryProcsFinished) @@ -63,12 +65,12 @@ func (server *httpServer) Start() { go httpServer.Serve(server.listener) } -//Stop the server +// Stop the server func (server *httpServer) Close() { server.listener.Close() } -//The address the server can be reached it. Pass this into the `ForwardingReporter`. +// The address the server can be reached it. Pass this into the `ForwardingReporter`. func (server *httpServer) Address() string { return "http://" + server.listener.Addr().String() } @@ -93,7 +95,7 @@ func (server *httpServer) RegisterAlive(node int, alive func() bool) { // Streaming Endpoints // -//The server will forward all received messages to Ginkgo reporters registered with `RegisterReporters` +// The server will forward all received messages to Ginkgo reporters registered with `RegisterReporters` func (server *httpServer) decode(writer http.ResponseWriter, request *http.Request, object interface{}) bool { defer request.Body.Close() if json.NewDecoder(request.Body).Decode(object) != nil { @@ -164,6 +166,23 @@ func (server *httpServer) emitProgressReport(writer http.ResponseWriter, request server.handleError(server.handler.EmitProgressReport(report, voidReceiver), writer) } +func (server *httpServer) handleReportBeforeSuiteCompleted(writer http.ResponseWriter, request *http.Request) { + var state types.SpecState + if !server.decode(writer, request, &state) { + return + } + + server.handleError(server.handler.ReportBeforeSuiteCompleted(state, voidReceiver), writer) +} + +func (server *httpServer) handleReportBeforeSuiteState(writer http.ResponseWriter, request *http.Request) { + var state types.SpecState + if server.handleError(server.handler.ReportBeforeSuiteState(voidSender, &state), writer) { + return + } + json.NewEncoder(writer).Encode(state) +} + func (server *httpServer) handleBeforeSuiteCompleted(writer http.ResponseWriter, request *http.Request) { var beforeSuiteState BeforeSuiteState if !server.decode(writer, request, &beforeSuiteState) { diff --git a/internal/parallel_support/rpc_client.go b/internal/parallel_support/rpc_client.go index fe93cc2b9..59e8e6fd0 100644 --- a/internal/parallel_support/rpc_client.go +++ b/internal/parallel_support/rpc_client.go @@ -76,6 +76,19 @@ func (client *rpcClient) PostEmitProgressReport(report types.ProgressReport) err return client.client.Call("Server.EmitProgressReport", report, voidReceiver) } +func (client *rpcClient) PostReportBeforeSuiteCompleted(state types.SpecState) error { + return client.client.Call("Server.ReportBeforeSuiteCompleted", state, voidReceiver) +} + +func (client *rpcClient) BlockUntilReportBeforeSuiteCompleted() (types.SpecState, error) { + var state types.SpecState + err := client.poll("Server.ReportBeforeSuiteState", &state) + if err == ErrorGone { + return types.SpecStateFailed, nil + } + return state, err +} + func (client *rpcClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { beforeSuiteState := BeforeSuiteState{ State: state, diff --git a/internal/parallel_support/server_handler.go b/internal/parallel_support/server_handler.go index 7c6e67b96..a6d98793e 100644 --- a/internal/parallel_support/server_handler.go +++ b/internal/parallel_support/server_handler.go @@ -18,16 +18,17 @@ var voidSender Void // It handles all the business logic to avoid duplication between the two servers type ServerHandler struct { - done chan interface{} - outputDestination io.Writer - reporter reporters.Reporter - alives []func() bool - lock *sync.Mutex - beforeSuiteState BeforeSuiteState - parallelTotal int - counter int - counterLock *sync.Mutex - shouldAbort bool + done chan interface{} + outputDestination io.Writer + reporter reporters.Reporter + alives []func() bool + lock *sync.Mutex + beforeSuiteState BeforeSuiteState + reportBeforeSuiteState types.SpecState + parallelTotal int + counter int + counterLock *sync.Mutex + shouldAbort bool numSuiteDidBegins int numSuiteDidEnds int @@ -37,11 +38,12 @@ type ServerHandler struct { func newServerHandler(parallelTotal int, reporter reporters.Reporter) *ServerHandler { return &ServerHandler{ - reporter: reporter, - lock: &sync.Mutex{}, - counterLock: &sync.Mutex{}, - alives: make([]func() bool, parallelTotal), - beforeSuiteState: BeforeSuiteState{Data: nil, State: types.SpecStateInvalid}, + reporter: reporter, + lock: &sync.Mutex{}, + counterLock: &sync.Mutex{}, + alives: make([]func() bool, parallelTotal), + beforeSuiteState: BeforeSuiteState{Data: nil, State: types.SpecStateInvalid}, + parallelTotal: parallelTotal, outputDestination: os.Stdout, done: make(chan interface{}), @@ -140,6 +142,29 @@ func (handler *ServerHandler) haveNonprimaryProcsFinished() bool { return true } +func (handler *ServerHandler) ReportBeforeSuiteCompleted(reportBeforeSuiteState types.SpecState, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + handler.reportBeforeSuiteState = reportBeforeSuiteState + + return nil +} + +func (handler *ServerHandler) ReportBeforeSuiteState(_ Void, reportBeforeSuiteState *types.SpecState) error { + proc1IsAlive := handler.procIsAlive(1) + handler.lock.Lock() + defer handler.lock.Unlock() + if handler.reportBeforeSuiteState == types.SpecStateInvalid { + if proc1IsAlive { + return ErrorEarly + } else { + return ErrorGone + } + } + *reportBeforeSuiteState = handler.reportBeforeSuiteState + return nil +} + func (handler *ServerHandler) BeforeSuiteCompleted(beforeSuiteState BeforeSuiteState, _ *Void) error { handler.lock.Lock() defer handler.lock.Unlock() diff --git a/internal/suite.go b/internal/suite.go index d3671ebcf..0cd40a059 100644 --- a/internal/suite.go +++ b/internal/suite.go @@ -129,7 +129,7 @@ func (suite *Suite) PushNode(node Node) error { return suite.pushCleanupNode(node) } - if node.NodeType.Is(types.NodeTypeBeforeSuite | types.NodeTypeAfterSuite | types.NodeTypeSynchronizedBeforeSuite | types.NodeTypeSynchronizedAfterSuite | types.NodeTypeReportAfterSuite) { + if node.NodeType.Is(types.NodeTypeBeforeSuite | types.NodeTypeAfterSuite | types.NodeTypeSynchronizedBeforeSuite | types.NodeTypeSynchronizedAfterSuite | types.NodeTypeBeforeSuite | types.NodeTypeReportBeforeSuite | types.NodeTypeReportAfterSuite) { return suite.pushSuiteNode(node) } @@ -222,7 +222,7 @@ func (suite *Suite) pushCleanupNode(node Node) error { node.NodeType = types.NodeTypeCleanupAfterSuite case types.NodeTypeBeforeAll, types.NodeTypeAfterAll: node.NodeType = types.NodeTypeCleanupAfterAll - case types.NodeTypeReportBeforeEach, types.NodeTypeReportAfterEach, types.NodeTypeReportAfterSuite: + case types.NodeTypeReportBeforeEach, types.NodeTypeReportAfterEach, types.NodeTypeReportBeforeSuite, types.NodeTypeReportAfterSuite: return types.GinkgoErrors.PushingCleanupInReportingNode(node.CodeLocation, suite.currentNode.NodeType) case types.NodeTypeCleanupInvalid, types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll, types.NodeTypeCleanupAfterSuite: return types.GinkgoErrors.PushingCleanupInCleanupNode(node.CodeLocation) @@ -408,7 +408,29 @@ func (suite *Suite) runSpecs(description string, suiteLabels Labels, suitePath s } suite.report.SuiteSucceeded = true - suite.runBeforeSuite(numSpecsThatWillBeRun) + + if len(suite.suiteNodes.WithType(types.NodeTypeReportBeforeSuite)) > 0 { + if suite.config.ParallelProcess == 1 { + suite.runReportBeforeSuite() + if suite.isRunningInParallel() { + if suite.report.SuiteSucceeded { + suite.client.PostReportBeforeSuiteCompleted(types.SpecStatePassed) + } else { + suite.client.PostReportBeforeSuiteCompleted(types.SpecStateFailed) + } + } + } else { + state, err := suite.client.BlockUntilReportBeforeSuiteCompleted() + if err != nil || state.Is(types.SpecStateFailed) { + suite.report.SuiteSucceeded = false + } + } + } + + ranBeforeSuite := suite.report.SuiteSucceeded + if suite.report.SuiteSucceeded { + suite.runBeforeSuite(numSpecsThatWillBeRun) + } if suite.report.SuiteSucceeded { groupedSpecIndices, serialGroupedSpecIndices := OrderSpecs(specs, suite.config) @@ -447,7 +469,9 @@ func (suite *Suite) runSpecs(description string, suiteLabels Labels, suitePath s } } - suite.runAfterSuiteCleanup(numSpecsThatWillBeRun) + if ranBeforeSuite { + suite.runAfterSuiteCleanup(numSpecsThatWillBeRun) + } interruptStatus := suite.interruptHandler.Status() if interruptStatus.Interrupted() { @@ -530,24 +554,6 @@ func (suite *Suite) runAfterSuiteCleanup(numSpecsThatWillBeRun int) { } } -func (suite *Suite) runReportAfterSuite() { - for _, node := range suite.suiteNodes.WithType(types.NodeTypeReportAfterSuite) { - suite.selectiveLock.Lock() - suite.currentSpecReport = types.SpecReport{ - LeafNodeType: node.NodeType, - LeafNodeLocation: node.CodeLocation, - LeafNodeText: node.Text, - ParallelProcess: suite.config.ParallelProcess, - RunningInParallel: suite.isRunningInParallel(), - } - suite.selectiveLock.Unlock() - - suite.reporter.WillRun(suite.currentSpecReport) - suite.runReportAfterSuiteNode(node, suite.report) - suite.processCurrentSpecReport() - } -} - func (suite *Suite) reportEach(spec Spec, nodeType types.NodeType) { nodes := spec.Nodes.WithType(nodeType) if nodeType == types.NodeTypeReportAfterEach { @@ -676,6 +682,58 @@ func (suite *Suite) runSuiteNode(node Node) { return } +// TODO - clean up and merge these four once the dust settles +func (suite *Suite) runReportBeforeSuite() { + for _, node := range suite.suiteNodes.WithType(types.NodeTypeReportBeforeSuite) { + suite.selectiveLock.Lock() + suite.currentSpecReport = types.SpecReport{ + LeafNodeType: node.NodeType, + LeafNodeLocation: node.CodeLocation, + ParallelProcess: suite.config.ParallelProcess, + RunningInParallel: suite.isRunningInParallel(), + } + suite.selectiveLock.Unlock() + + suite.reporter.WillRun(suite.currentSpecReport) + suite.runReportBeforeSuiteNode(node, suite.report) + suite.processCurrentSpecReport() + } +} + +func (suite *Suite) runReportBeforeSuiteNode(node Node, report types.Report) { + suite.writer.Truncate() + suite.outputInterceptor.StartInterceptingOutput() + suite.currentSpecReport.StartTime = time.Now() + + node.Body = func(SpecContext) { node.ReportSuiteBody(report) } + suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + + suite.currentSpecReport.EndTime = time.Now() + suite.currentSpecReport.RunTime = suite.currentSpecReport.EndTime.Sub(suite.currentSpecReport.StartTime) + suite.currentSpecReport.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) + suite.currentSpecReport.CapturedStdOutErr = suite.outputInterceptor.StopInterceptingAndReturnOutput() + + return +} + +func (suite *Suite) runReportAfterSuite() { + for _, node := range suite.suiteNodes.WithType(types.NodeTypeReportAfterSuite) { + suite.selectiveLock.Lock() + suite.currentSpecReport = types.SpecReport{ + LeafNodeType: node.NodeType, + LeafNodeLocation: node.CodeLocation, + LeafNodeText: node.Text, + ParallelProcess: suite.config.ParallelProcess, + RunningInParallel: suite.isRunningInParallel(), + } + suite.selectiveLock.Unlock() + + suite.reporter.WillRun(suite.currentSpecReport) + suite.runReportAfterSuiteNode(node, suite.report) + suite.processCurrentSpecReport() + } +} + func (suite *Suite) runReportAfterSuiteNode(node Node, report types.Report) { suite.writer.Truncate() suite.outputInterceptor.StartInterceptingOutput() @@ -691,7 +749,7 @@ func (suite *Suite) runReportAfterSuiteNode(node Node, report types.Report) { report = report.Add(aggregatedReport) } - node.Body = func(SpecContext) { node.ReportAfterSuiteBody(report) } + node.Body = func(SpecContext) { node.ReportSuiteBody(report) } suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") suite.currentSpecReport.EndTime = time.Now() diff --git a/internal/suite_test.go b/internal/suite_test.go index a74125720..81b915f7c 100644 --- a/internal/suite_test.go +++ b/internal/suite_test.go @@ -330,6 +330,28 @@ var _ = Describe("Suite", func() { }) }) + Context("when pushing a cleanup node in a ReportBeforeSuite node", func() { + It("errors", func() { + var errors = make([]error, 4) + reportBeforeSuiteNode := N(types.NodeTypeReportBeforeSuite, "", func(_ types.Report) { + errors[3] = suite.PushNode(N(types.NodeTypeCleanupInvalid, cl)) + }, types.NewCodeLocation(0)) + + errors[0] = suite.PushNode(N(ntCon, "container", func() { + errors[2] = suite.PushNode(N(ntIt, "test")) + })) + errors[1] = suite.PushNode(reportBeforeSuiteNode) + Ω(errors[0]).ShouldNot(HaveOccurred()) + Ω(errors[1]).ShouldNot(HaveOccurred()) + + Ω(suite.BuildTree()).Should(Succeed()) + Ω(errors[2]).ShouldNot(HaveOccurred()) + + suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) + Ω(errors[3]).Should(MatchError(types.GinkgoErrors.PushingCleanupInReportingNode(cl, types.NodeTypeReportBeforeSuite))) + }) + }) + Context("when pushing a cleanup node in a ReportAfterSuite node", func() { It("errors", func() { var errors = make([]error, 4) diff --git a/reporting_dsl.go b/reporting_dsl.go index afc151b13..f33786a2d 100644 --- a/reporting_dsl.go +++ b/reporting_dsl.go @@ -35,7 +35,7 @@ func CurrentSpecReport() SpecReport { } /* - ReportEntryVisibility governs the visibility of ReportEntries in Ginkgo's console reporter + ReportEntryVisibility governs the visibility of ReportEntries in Ginkgo's console reporter - ReportEntryVisibilityAlways: the default behavior - the ReportEntry is always emitted. - ReportEntryVisibilityFailureOrVerbose: the ReportEntry is only emitted if the spec fails or if the tests are run with -v (similar to GinkgoWriters behavior). @@ -50,9 +50,9 @@ const ReportEntryVisibilityAlways, ReportEntryVisibilityFailureOrVerbose, Report /* AddReportEntry generates and adds a new ReportEntry to the current spec's SpecReport. It can take any of the following arguments: - - A single arbitrary object to attach as the Value of the ReportEntry. This object will be included in any generated reports and will be emitted to the console when the report is emitted. - - A ReportEntryVisibility enum to control the visibility of the ReportEntry - - An Offset or CodeLocation decoration to control the reported location of the ReportEntry + - A single arbitrary object to attach as the Value of the ReportEntry. This object will be included in any generated reports and will be emitted to the console when the report is emitted. + - A ReportEntryVisibility enum to control the visibility of the ReportEntry + - An Offset or CodeLocation decoration to control the reported location of the ReportEntry If the Value object implements `fmt.Stringer`, it's `String()` representation is used when emitting to the console. @@ -100,6 +100,25 @@ func ReportAfterEach(body func(SpecReport), args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterEach, "", combinedArgs...)) } +/* +ReportBeforeSuite nodes are run at the beginning of the suite. ReportBeforeSuite nodes take a function that receives a suite Report. + +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) + +# When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportBeforeSuite + +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 +*/ +func ReportBeforeSuite(body func(Report), args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportBeforeSuite, "", combinedArgs...)) +} + /* ReportAfterSuite nodes are run at the end of the suite. ReportAfterSuite nodes take a function that receives a suite Report. @@ -113,6 +132,7 @@ In addition to using ReportAfterSuite to programmatically generate suite reports 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 */ func ReportAfterSuite(text string, body func(Report), args ...interface{}) bool { diff --git a/types/errors.go b/types/errors.go index b7ed5a21e..a0f59fb4b 100644 --- a/types/errors.go +++ b/types/errors.go @@ -108,8 +108,8 @@ Please ensure all assertions are inside leaf nodes such as {{bold}}BeforeEach{{/ func (g ginkgoErrors) SuiteNodeInNestedContext(nodeType NodeType, cl CodeLocation) error { docLink := "suite-setup-and-cleanup-beforesuite-and-aftersuite" - if nodeType.Is(NodeTypeReportAfterSuite) { - docLink = "reporting-nodes---reportaftersuite" + if nodeType.Is(NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite) { + docLink = "reporting-nodes---reportbeforesuite-and-reportaftersuite" } return GinkgoError{ @@ -125,8 +125,8 @@ func (g ginkgoErrors) SuiteNodeInNestedContext(nodeType NodeType, cl CodeLocatio func (g ginkgoErrors) SuiteNodeDuringRunPhase(nodeType NodeType, cl CodeLocation) error { docLink := "suite-setup-and-cleanup-beforesuite-and-aftersuite" - if nodeType.Is(NodeTypeReportAfterSuite) { - docLink = "reporting-nodes---reportaftersuite" + if nodeType.Is(NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite) { + docLink = "reporting-nodes---reportbeforesuite-and-reportaftersuite" } return GinkgoError{ @@ -320,7 +320,7 @@ func (g ginkgoErrors) PushingCleanupNodeDuringTreeConstruction(cl CodeLocation) func (g ginkgoErrors) PushingCleanupInReportingNode(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: fmt.Sprintf("DeferCleanup cannot be called in %s", nodeType), - Message: "Please inline your cleanup code - Ginkgo won't run cleanup code after a ReportAfterEach or ReportAfterSuite.", + Message: "Please inline your cleanup code - Ginkgo won't run cleanup code after a Reporting node.", CodeLocation: cl, DocLink: "cleaning-up-our-cleanup-code-defercleanup", } diff --git a/types/types.go b/types/types.go index a388f70c0..3e979ba8c 100644 --- a/types/types.go +++ b/types/types.go @@ -58,6 +58,7 @@ type Report struct { SuiteConfig SuiteConfig //SpecReports is a list of all SpecReports generated by this test run + //It is empty when the SuiteReport is provided to ReportBeforeSuite SpecReports SpecReports } @@ -761,6 +762,7 @@ const ( NodeTypeReportBeforeEach NodeTypeReportAfterEach + NodeTypeReportBeforeSuite NodeTypeReportAfterSuite NodeTypeCleanupInvalid @@ -770,9 +772,9 @@ const ( ) var NodeTypesForContainerAndIt = NodeTypeContainer | NodeTypeIt -var NodeTypesForSuiteLevelNodes = NodeTypeBeforeSuite | NodeTypeSynchronizedBeforeSuite | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeReportAfterSuite | NodeTypeCleanupAfterSuite +var NodeTypesForSuiteLevelNodes = NodeTypeBeforeSuite | NodeTypeSynchronizedBeforeSuite | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite | NodeTypeCleanupAfterSuite var NodeTypesAllowedDuringCleanupInterrupt = NodeTypeAfterEach | NodeTypeJustAfterEach | NodeTypeAfterAll | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeCleanupAfterEach | NodeTypeCleanupAfterAll | NodeTypeCleanupAfterSuite -var NodeTypesAllowedDuringReportInterrupt = NodeTypeReportBeforeEach | NodeTypeReportAfterEach | NodeTypeReportAfterSuite +var NodeTypesAllowedDuringReportInterrupt = NodeTypeReportBeforeEach | NodeTypeReportAfterEach | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite var ntEnumSupport = NewEnumSupport(map[uint]string{ uint(NodeTypeInvalid): "INVALID NODE TYPE", @@ -790,6 +792,7 @@ var ntEnumSupport = NewEnumSupport(map[uint]string{ uint(NodeTypeSynchronizedAfterSuite): "SynchronizedAfterSuite", uint(NodeTypeReportBeforeEach): "ReportBeforeEach", uint(NodeTypeReportAfterEach): "ReportAfterEach", + uint(NodeTypeReportBeforeSuite): "ReportBeforeSuite", uint(NodeTypeReportAfterSuite): "ReportAfterSuite", uint(NodeTypeCleanupInvalid): "DeferCleanup", uint(NodeTypeCleanupAfterEach): "DeferCleanup (Each)", diff --git a/types/types_test.go b/types/types_test.go index 53ec9a3eb..56921bf85 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -133,6 +133,7 @@ var _ = Describe("Types", func() { Entry(nil, types.NodeTypeSynchronizedAfterSuite, "SynchronizedAfterSuite"), Entry(nil, types.NodeTypeReportBeforeEach, "ReportBeforeEach"), Entry(nil, types.NodeTypeReportAfterEach, "ReportAfterEach"), + Entry(nil, types.NodeTypeReportBeforeSuite, "ReportBeforeSuite"), Entry(nil, types.NodeTypeReportAfterSuite, "ReportAfterSuite"), Entry(nil, types.NodeTypeCleanupInvalid, "DeferCleanup"), Entry(nil, types.NodeTypeCleanupAfterEach, "DeferCleanup (Each)"),