Skip to content

Commit

Permalink
ReportBeforeSuite provides the suite report before the suite begins
Browse files Browse the repository at this point in the history
  • Loading branch information
onsi committed Dec 10, 2022
1 parent 2165648 commit 52b4b9c
Show file tree
Hide file tree
Showing 22 changed files with 793 additions and 353 deletions.
17 changes: 10 additions & 7 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion dsl/reporting/reporting_dsl.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -27,4 +27,5 @@ var AddReportEntry = ginkgo.AddReportEntry

var ReportBeforeEach = ginkgo.ReportBeforeEach
var ReportAfterEach = ginkgo.ReportAfterEach
var ReportBeforeSuite = ginkgo.ReportBeforeSuite
var ReportAfterSuite = ginkgo.ReportAfterSuite
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
42 changes: 36 additions & 6 deletions integration/reporting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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))
Expand All @@ -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())
Expand Down Expand Up @@ -194,14 +223,15 @@ 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))
Ω(suite.Failures).Should(Equal(3))
Ω(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"))
Expand Down Expand Up @@ -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))
Expand Down
8 changes: 8 additions & 0 deletions internal/internal_integration/config_dry_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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") })
Expand All @@ -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
Expand All @@ -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())
Expand Down
11 changes: 10 additions & 1 deletion internal/internal_integration/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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))
})
})
})
Loading

0 comments on commit 52b4b9c

Please sign in to comment.