Skip to content

Commit

Permalink
add SpecContext to other reporting nodes and update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
eugenenosenko authored and onsi committed Feb 27, 2024
1 parent 06de431 commit c4e219f
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 37 deletions.
22 changes: 17 additions & 5 deletions internal/internal_integration/report_each_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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",
))
})
Expand Down Expand Up @@ -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")))
})
Expand Down
36 changes: 32 additions & 4 deletions internal/internal_integration/report_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
21 changes: 13 additions & 8 deletions internal/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
}
Expand Down
8 changes: 4 additions & 4 deletions internal/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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())
Expand Down Expand Up @@ -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)
})
Expand Down Expand Up @@ -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() {
Expand Down
12 changes: 6 additions & 6 deletions internal/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]))

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
51 changes: 41 additions & 10 deletions reporting_dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,34 +74,61 @@ 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...)

return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportBeforeEach, "", combinedArgs...))
}

/*
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...)

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.
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)
Expand All @@ -112,21 +139,25 @@ 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...))
}

/*
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)
Expand Down

0 comments on commit c4e219f

Please sign in to comment.