mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-12-28 16:00:22 +00:00
171ba6a65d
Bumps the github-dependencies group with 8 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/IBM/keyprotect-go-client](https://github.com/IBM/keyprotect-go-client) | `0.12.2` | `0.14.1` | | [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) | `1.53.14` | `1.54.6` | | [github.com/aws/aws-sdk-go-v2/service/sts](https://github.com/aws/aws-sdk-go-v2) | `1.28.1` | `1.29.1` | | [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) | `1.12.0` | `1.14.0` | | [github.com/kubernetes-csi/csi-lib-utils](https://github.com/kubernetes-csi/csi-lib-utils) | `0.17.0` | `0.18.1` | | [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) | `2.17.1` | `2.19.0` | | [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) | `1.18.0` | `1.19.1` | | [github.com/Azure/azure-sdk-for-go/sdk/azidentity](https://github.com/Azure/azure-sdk-for-go) | `1.6.0` | `1.7.0` | Updates `github.com/IBM/keyprotect-go-client` from 0.12.2 to 0.14.1 - [Release notes](https://github.com/IBM/keyprotect-go-client/releases) - [Changelog](https://github.com/IBM/keyprotect-go-client/blob/master/CHANGELOG.md) - [Commits](https://github.com/IBM/keyprotect-go-client/compare/v0.12.2...v0.14.1) Updates `github.com/aws/aws-sdk-go` from 1.53.14 to 1.54.6 - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.53.14...v1.54.6) Updates `github.com/aws/aws-sdk-go-v2/service/sts` from 1.28.1 to 1.29.1 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ecr/v1.28.1...service/s3/v1.29.1) Updates `github.com/hashicorp/vault/api` from 1.12.0 to 1.14.0 - [Release notes](https://github.com/hashicorp/vault/releases) - [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault/compare/v1.12.0...v1.14.0) Updates `github.com/kubernetes-csi/csi-lib-utils` from 0.17.0 to 0.18.1 - [Release notes](https://github.com/kubernetes-csi/csi-lib-utils/releases) - [Commits](https://github.com/kubernetes-csi/csi-lib-utils/compare/v0.17.0...v0.18.1) Updates `github.com/onsi/ginkgo/v2` from 2.17.1 to 2.19.0 - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.17.1...v2.19.0) Updates `github.com/onsi/gomega` from 1.32.0 to 1.33.1 - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.1) Updates `github.com/prometheus/client_golang` from 1.18.0 to 1.19.1 - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.18.0...v1.19.1) Updates `github.com/Azure/azure-sdk-for-go/sdk/azidentity` from 1.6.0 to 1.7.0 - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.6.0...sdk/azcore/v1.7.0) --- updated-dependencies: - dependency-name: github.com/IBM/keyprotect-go-client dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/service/sts dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/hashicorp/vault/api dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/kubernetes-csi/csi-lib-utils dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/onsi/ginkgo/v2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azidentity dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
1047 lines
41 KiB
Go
1047 lines
41 KiB
Go
package internal
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/onsi/ginkgo/v2/internal/interrupt_handler"
|
|
"github.com/onsi/ginkgo/v2/internal/parallel_support"
|
|
"github.com/onsi/ginkgo/v2/reporters"
|
|
"github.com/onsi/ginkgo/v2/types"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type Phase uint
|
|
|
|
const (
|
|
PhaseBuildTopLevel Phase = iota
|
|
PhaseBuildTree
|
|
PhaseRun
|
|
)
|
|
|
|
var PROGRESS_REPORTER_DEADLING = 5 * time.Second
|
|
|
|
type Suite struct {
|
|
tree *TreeNode
|
|
topLevelContainers Nodes
|
|
|
|
*ProgressReporterManager
|
|
|
|
phase Phase
|
|
|
|
suiteNodes Nodes
|
|
cleanupNodes Nodes
|
|
|
|
failer *Failer
|
|
reporter reporters.Reporter
|
|
writer WriterInterface
|
|
outputInterceptor OutputInterceptor
|
|
interruptHandler interrupt_handler.InterruptHandlerInterface
|
|
config types.SuiteConfig
|
|
deadline time.Time
|
|
|
|
skipAll bool
|
|
report types.Report
|
|
currentSpecReport types.SpecReport
|
|
currentNode Node
|
|
currentNodeStartTime time.Time
|
|
|
|
currentSpecContext *specContext
|
|
|
|
currentByStep types.SpecEvent
|
|
timelineOrder int
|
|
|
|
/*
|
|
We don't need to lock around all operations. Just those that *could* happen concurrently.
|
|
|
|
Suite, generally, only runs one node at a time - and so the possibiity for races is small. In fact, the presence of a race usually indicates the user has launched a goroutine that has leaked past the node it was launched in.
|
|
|
|
However, there are some operations that can happen concurrently:
|
|
|
|
- AddReportEntry and CurrentSpecReport can be accessed at any point by the user - including in goroutines that outlive the node intentionally (see, e.g. #1020). They both form a self-contained read-write pair and so a lock in them is sufficent.
|
|
- generateProgressReport can be invoked at any point in time by an interrupt or a progres poll. Moreover, it requires access to currentSpecReport, currentNode, currentNodeStartTime, and progressStepCursor. To make it threadsafe we need to lock around generateProgressReport when we read those variables _and_ everywhere those variables are *written*. In general we don't need to worry about all possible field writes to these variables as what `generateProgressReport` does with these variables is fairly selective (hence the name of the lock). Specifically, we dont' need to lock around state and failure message changes on `currentSpecReport` - just the setting of the variable itself.
|
|
*/
|
|
selectiveLock *sync.Mutex
|
|
|
|
client parallel_support.Client
|
|
}
|
|
|
|
func NewSuite() *Suite {
|
|
return &Suite{
|
|
tree: &TreeNode{},
|
|
phase: PhaseBuildTopLevel,
|
|
ProgressReporterManager: NewProgressReporterManager(),
|
|
|
|
selectiveLock: &sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) Clone() (*Suite, error) {
|
|
if suite.phase != PhaseBuildTopLevel {
|
|
return nil, fmt.Errorf("cannot clone suite after tree has been built")
|
|
}
|
|
return &Suite{
|
|
tree: &TreeNode{},
|
|
phase: PhaseBuildTopLevel,
|
|
ProgressReporterManager: NewProgressReporterManager(),
|
|
topLevelContainers: suite.topLevelContainers.Clone(),
|
|
suiteNodes: suite.suiteNodes.Clone(),
|
|
selectiveLock: &sync.Mutex{},
|
|
}, nil
|
|
}
|
|
|
|
func (suite *Suite) BuildTree() error {
|
|
// During PhaseBuildTopLevel, the top level containers are stored in suite.topLevelCotainers and entered
|
|
// We now enter PhaseBuildTree where these top level containers are entered and added to the spec tree
|
|
suite.phase = PhaseBuildTree
|
|
for _, topLevelContainer := range suite.topLevelContainers {
|
|
err := suite.PushNode(topLevelContainer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (suite *Suite) Run(description string, suiteLabels Labels, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) {
|
|
if suite.phase != PhaseBuildTree {
|
|
panic("cannot run before building the tree = call suite.BuildTree() first")
|
|
}
|
|
ApplyNestedFocusPolicyToTree(suite.tree)
|
|
specs := GenerateSpecsFromTreeRoot(suite.tree)
|
|
specs, hasProgrammaticFocus := ApplyFocusToSpecs(specs, description, suiteLabels, suiteConfig)
|
|
|
|
suite.phase = PhaseRun
|
|
suite.client = client
|
|
suite.failer = failer
|
|
suite.reporter = reporter
|
|
suite.writer = writer
|
|
suite.outputInterceptor = outputInterceptor
|
|
suite.interruptHandler = interruptHandler
|
|
suite.config = suiteConfig
|
|
|
|
if suite.config.Timeout > 0 {
|
|
suite.deadline = time.Now().Add(suite.config.Timeout)
|
|
}
|
|
|
|
cancelProgressHandler := progressSignalRegistrar(suite.handleProgressSignal)
|
|
|
|
success := suite.runSpecs(description, suiteLabels, suitePath, hasProgrammaticFocus, specs)
|
|
|
|
cancelProgressHandler()
|
|
|
|
return success, hasProgrammaticFocus
|
|
}
|
|
|
|
func (suite *Suite) InRunPhase() bool {
|
|
return suite.phase == PhaseRun
|
|
}
|
|
|
|
/*
|
|
Tree Construction methods
|
|
|
|
PushNode is used during PhaseBuildTopLevel and PhaseBuildTree
|
|
*/
|
|
|
|
func (suite *Suite) PushNode(node Node) error {
|
|
if node.NodeType.Is(types.NodeTypeCleanupInvalid | types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll | types.NodeTypeCleanupAfterSuite) {
|
|
return suite.pushCleanupNode(node)
|
|
}
|
|
|
|
if node.NodeType.Is(types.NodeTypeBeforeSuite | types.NodeTypeAfterSuite | types.NodeTypeSynchronizedBeforeSuite | types.NodeTypeSynchronizedAfterSuite | types.NodeTypeBeforeSuite | types.NodeTypeReportBeforeSuite | types.NodeTypeReportAfterSuite) {
|
|
return suite.pushSuiteNode(node)
|
|
}
|
|
|
|
if suite.phase == PhaseRun {
|
|
return types.GinkgoErrors.PushingNodeInRunPhase(node.NodeType, node.CodeLocation)
|
|
}
|
|
|
|
if node.MarkedSerial {
|
|
firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered()
|
|
if !firstOrderedNode.IsZero() && !firstOrderedNode.MarkedSerial {
|
|
return types.GinkgoErrors.InvalidSerialNodeInNonSerialOrderedContainer(node.CodeLocation, node.NodeType)
|
|
}
|
|
}
|
|
|
|
if node.NodeType.Is(types.NodeTypeBeforeAll | types.NodeTypeAfterAll) {
|
|
firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered()
|
|
if firstOrderedNode.IsZero() {
|
|
return types.GinkgoErrors.SetupNodeNotInOrderedContainer(node.CodeLocation, node.NodeType)
|
|
}
|
|
}
|
|
|
|
if node.MarkedContinueOnFailure {
|
|
firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered()
|
|
if !firstOrderedNode.IsZero() {
|
|
return types.GinkgoErrors.InvalidContinueOnFailureDecoration(node.CodeLocation)
|
|
}
|
|
}
|
|
|
|
if node.NodeType == types.NodeTypeContainer {
|
|
// During PhaseBuildTopLevel we only track the top level containers without entering them
|
|
// We only enter the top level container nodes during PhaseBuildTree
|
|
//
|
|
// This ensures the tree is only constructed after `go spec` has called `flag.Parse()` and gives
|
|
// the user an opportunity to load suiteConfiguration information in the `TestX` go spec hook just before `RunSpecs`
|
|
// is invoked. This makes the lifecycle easier to reason about and solves issues like #693.
|
|
if suite.phase == PhaseBuildTopLevel {
|
|
suite.topLevelContainers = append(suite.topLevelContainers, node)
|
|
return nil
|
|
}
|
|
if suite.phase == PhaseBuildTree {
|
|
parentTree := suite.tree
|
|
suite.tree = &TreeNode{Node: node}
|
|
parentTree.AppendChild(suite.tree)
|
|
err := func() (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = types.GinkgoErrors.CaughtPanicDuringABuildPhase(e, node.CodeLocation)
|
|
}
|
|
}()
|
|
node.Body(nil)
|
|
return err
|
|
}()
|
|
suite.tree = parentTree
|
|
return err
|
|
}
|
|
} else {
|
|
suite.tree.AppendChild(&TreeNode{Node: node})
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (suite *Suite) pushSuiteNode(node Node) error {
|
|
if suite.phase == PhaseBuildTree {
|
|
return types.GinkgoErrors.SuiteNodeInNestedContext(node.NodeType, node.CodeLocation)
|
|
}
|
|
|
|
if suite.phase == PhaseRun {
|
|
return types.GinkgoErrors.SuiteNodeDuringRunPhase(node.NodeType, node.CodeLocation)
|
|
}
|
|
|
|
switch node.NodeType {
|
|
case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite:
|
|
existingBefores := suite.suiteNodes.WithType(types.NodeTypeBeforeSuite | types.NodeTypeSynchronizedBeforeSuite)
|
|
if len(existingBefores) > 0 {
|
|
return types.GinkgoErrors.MultipleBeforeSuiteNodes(node.NodeType, node.CodeLocation, existingBefores[0].NodeType, existingBefores[0].CodeLocation)
|
|
}
|
|
case types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite:
|
|
existingAfters := suite.suiteNodes.WithType(types.NodeTypeAfterSuite | types.NodeTypeSynchronizedAfterSuite)
|
|
if len(existingAfters) > 0 {
|
|
return types.GinkgoErrors.MultipleAfterSuiteNodes(node.NodeType, node.CodeLocation, existingAfters[0].NodeType, existingAfters[0].CodeLocation)
|
|
}
|
|
}
|
|
|
|
suite.suiteNodes = append(suite.suiteNodes, node)
|
|
return nil
|
|
}
|
|
|
|
func (suite *Suite) pushCleanupNode(node Node) error {
|
|
if suite.phase != PhaseRun || suite.currentNode.IsZero() {
|
|
return types.GinkgoErrors.PushingCleanupNodeDuringTreeConstruction(node.CodeLocation)
|
|
}
|
|
|
|
switch suite.currentNode.NodeType {
|
|
case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite, types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite:
|
|
node.NodeType = types.NodeTypeCleanupAfterSuite
|
|
case types.NodeTypeBeforeAll, types.NodeTypeAfterAll:
|
|
node.NodeType = types.NodeTypeCleanupAfterAll
|
|
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)
|
|
default:
|
|
node.NodeType = types.NodeTypeCleanupAfterEach
|
|
}
|
|
|
|
node.NodeIDWhereCleanupWasGenerated = suite.currentNode.ID
|
|
node.NestingLevel = suite.currentNode.NestingLevel
|
|
suite.selectiveLock.Lock()
|
|
suite.cleanupNodes = append(suite.cleanupNodes, node)
|
|
suite.selectiveLock.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (suite *Suite) generateTimelineLocation() types.TimelineLocation {
|
|
suite.selectiveLock.Lock()
|
|
defer suite.selectiveLock.Unlock()
|
|
|
|
suite.timelineOrder += 1
|
|
return types.TimelineLocation{
|
|
Offset: len(suite.currentSpecReport.CapturedGinkgoWriterOutput) + suite.writer.Len(),
|
|
Order: suite.timelineOrder,
|
|
Time: time.Now(),
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) handleSpecEvent(event types.SpecEvent) types.SpecEvent {
|
|
event.TimelineLocation = suite.generateTimelineLocation()
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecReport.SpecEvents = append(suite.currentSpecReport.SpecEvents, event)
|
|
suite.selectiveLock.Unlock()
|
|
suite.reporter.EmitSpecEvent(event)
|
|
return event
|
|
}
|
|
|
|
func (suite *Suite) handleSpecEventEnd(eventType types.SpecEventType, startEvent types.SpecEvent) {
|
|
event := startEvent
|
|
event.SpecEventType = eventType
|
|
event.TimelineLocation = suite.generateTimelineLocation()
|
|
event.Duration = event.TimelineLocation.Time.Sub(startEvent.TimelineLocation.Time)
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecReport.SpecEvents = append(suite.currentSpecReport.SpecEvents, event)
|
|
suite.selectiveLock.Unlock()
|
|
suite.reporter.EmitSpecEvent(event)
|
|
}
|
|
|
|
func (suite *Suite) By(text string, callback ...func()) error {
|
|
cl := types.NewCodeLocation(2)
|
|
if suite.phase != PhaseRun {
|
|
return types.GinkgoErrors.ByNotDuringRunPhase(cl)
|
|
}
|
|
|
|
event := suite.handleSpecEvent(types.SpecEvent{
|
|
SpecEventType: types.SpecEventByStart,
|
|
CodeLocation: cl,
|
|
Message: text,
|
|
})
|
|
suite.selectiveLock.Lock()
|
|
suite.currentByStep = event
|
|
suite.selectiveLock.Unlock()
|
|
|
|
if len(callback) == 1 {
|
|
defer func() {
|
|
suite.selectiveLock.Lock()
|
|
suite.currentByStep = types.SpecEvent{}
|
|
suite.selectiveLock.Unlock()
|
|
suite.handleSpecEventEnd(types.SpecEventByEnd, event)
|
|
}()
|
|
callback[0]()
|
|
} else if len(callback) > 1 {
|
|
panic("just one callback per By, please")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
Spec Running methods - used during PhaseRun
|
|
*/
|
|
func (suite *Suite) CurrentSpecReport() types.SpecReport {
|
|
suite.selectiveLock.Lock()
|
|
defer suite.selectiveLock.Unlock()
|
|
report := suite.currentSpecReport
|
|
if suite.writer != nil {
|
|
report.CapturedGinkgoWriterOutput = string(suite.writer.Bytes())
|
|
}
|
|
report.ReportEntries = make([]ReportEntry, len(report.ReportEntries))
|
|
copy(report.ReportEntries, suite.currentSpecReport.ReportEntries)
|
|
return report
|
|
}
|
|
|
|
// Only valid in the preview context. In general suite.report only includes
|
|
// the specs run by _this_ node - it is only at the end of the suite that
|
|
// the parallel reports are aggregated. However in the preview context we run
|
|
// in series and
|
|
func (suite *Suite) GetPreviewReport() types.Report {
|
|
suite.selectiveLock.Lock()
|
|
defer suite.selectiveLock.Unlock()
|
|
return suite.report
|
|
}
|
|
|
|
func (suite *Suite) AddReportEntry(entry ReportEntry) error {
|
|
if suite.phase != PhaseRun {
|
|
return types.GinkgoErrors.AddReportEntryNotDuringRunPhase(entry.Location)
|
|
}
|
|
entry.TimelineLocation = suite.generateTimelineLocation()
|
|
entry.Time = entry.TimelineLocation.Time
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecReport.ReportEntries = append(suite.currentSpecReport.ReportEntries, entry)
|
|
suite.selectiveLock.Unlock()
|
|
suite.reporter.EmitReportEntry(entry)
|
|
return nil
|
|
}
|
|
|
|
func (suite *Suite) generateProgressReport(fullReport bool) types.ProgressReport {
|
|
timelineLocation := suite.generateTimelineLocation()
|
|
suite.selectiveLock.Lock()
|
|
defer suite.selectiveLock.Unlock()
|
|
|
|
deadline, cancel := context.WithTimeout(context.Background(), PROGRESS_REPORTER_DEADLING)
|
|
defer cancel()
|
|
var additionalReports []string
|
|
if suite.currentSpecContext != nil {
|
|
additionalReports = append(additionalReports, suite.currentSpecContext.QueryProgressReporters(deadline, suite.failer)...)
|
|
}
|
|
additionalReports = append(additionalReports, suite.QueryProgressReporters(deadline, suite.failer)...)
|
|
gwOutput := suite.currentSpecReport.CapturedGinkgoWriterOutput + string(suite.writer.Bytes())
|
|
pr, err := NewProgressReport(suite.isRunningInParallel(), suite.currentSpecReport, suite.currentNode, suite.currentNodeStartTime, suite.currentByStep, gwOutput, timelineLocation, additionalReports, suite.config.SourceRoots, fullReport)
|
|
|
|
if err != nil {
|
|
fmt.Printf("{{red}}Failed to generate progress report:{{/}}\n%s\n", err.Error())
|
|
}
|
|
return pr
|
|
}
|
|
|
|
func (suite *Suite) handleProgressSignal() {
|
|
report := suite.generateProgressReport(false)
|
|
report.Message = "{{bold}}You've requested a progress report:{{/}}"
|
|
suite.emitProgressReport(report)
|
|
}
|
|
|
|
func (suite *Suite) emitProgressReport(report types.ProgressReport) {
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecReport.ProgressReports = append(suite.currentSpecReport.ProgressReports, report.WithoutCapturedGinkgoWriterOutput())
|
|
suite.selectiveLock.Unlock()
|
|
|
|
suite.reporter.EmitProgressReport(report)
|
|
if suite.isRunningInParallel() {
|
|
err := suite.client.PostEmitProgressReport(report)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) isRunningInParallel() bool {
|
|
return suite.config.ParallelTotal > 1
|
|
}
|
|
|
|
func (suite *Suite) processCurrentSpecReport() {
|
|
suite.reporter.DidRun(suite.currentSpecReport)
|
|
if suite.isRunningInParallel() {
|
|
suite.client.PostDidRun(suite.currentSpecReport)
|
|
}
|
|
suite.report.SpecReports = append(suite.report.SpecReports, suite.currentSpecReport)
|
|
|
|
if suite.currentSpecReport.State.Is(types.SpecStateFailureStates) {
|
|
suite.report.SuiteSucceeded = false
|
|
if suite.config.FailFast || suite.currentSpecReport.State.Is(types.SpecStateAborted) {
|
|
suite.skipAll = true
|
|
if suite.isRunningInParallel() {
|
|
suite.client.PostAbort()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) runSpecs(description string, suiteLabels Labels, suitePath string, hasProgrammaticFocus bool, specs Specs) bool {
|
|
numSpecsThatWillBeRun := specs.CountWithoutSkip()
|
|
|
|
suite.report = types.Report{
|
|
SuitePath: suitePath,
|
|
SuiteDescription: description,
|
|
SuiteLabels: suiteLabels,
|
|
SuiteConfig: suite.config,
|
|
SuiteHasProgrammaticFocus: hasProgrammaticFocus,
|
|
PreRunStats: types.PreRunStats{
|
|
TotalSpecs: len(specs),
|
|
SpecsThatWillRun: numSpecsThatWillBeRun,
|
|
},
|
|
StartTime: time.Now(),
|
|
}
|
|
|
|
suite.reporter.SuiteWillBegin(suite.report)
|
|
if suite.isRunningInParallel() {
|
|
suite.client.PostSuiteWillBegin(suite.report)
|
|
}
|
|
|
|
suite.report.SuiteSucceeded = true
|
|
|
|
suite.runReportSuiteNodesIfNeedBe(types.NodeTypeReportBeforeSuite)
|
|
|
|
ranBeforeSuite := suite.report.SuiteSucceeded
|
|
if suite.report.SuiteSucceeded {
|
|
suite.runBeforeSuite(numSpecsThatWillBeRun)
|
|
}
|
|
|
|
if suite.report.SuiteSucceeded {
|
|
groupedSpecIndices, serialGroupedSpecIndices := OrderSpecs(specs, suite.config)
|
|
nextIndex := MakeIncrementingIndexCounter()
|
|
if suite.isRunningInParallel() {
|
|
nextIndex = suite.client.FetchNextCounter
|
|
}
|
|
|
|
for {
|
|
groupedSpecIdx, err := nextIndex()
|
|
if err != nil {
|
|
suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, fmt.Sprintf("Failed to iterate over specs:\n%s", err.Error()))
|
|
suite.report.SuiteSucceeded = false
|
|
break
|
|
}
|
|
|
|
if groupedSpecIdx >= len(groupedSpecIndices) {
|
|
if suite.config.ParallelProcess == 1 && len(serialGroupedSpecIndices) > 0 {
|
|
groupedSpecIndices, serialGroupedSpecIndices, nextIndex = serialGroupedSpecIndices, GroupedSpecIndices{}, MakeIncrementingIndexCounter()
|
|
suite.client.BlockUntilNonprimaryProcsHaveFinished()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
// the complexity for running groups of specs is very high because of Ordered containers and FlakeAttempts
|
|
// we encapsulate that complexity in the notion of a Group that can run
|
|
// Group is really just an extension of suite so it gets passed a suite and has access to all its internals
|
|
// Note that group is stateful and intended for single use!
|
|
newGroup(suite).run(specs.AtIndices(groupedSpecIndices[groupedSpecIdx]))
|
|
}
|
|
|
|
if suite.config.FailOnPending && specs.HasAnySpecsMarkedPending() {
|
|
suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Detected pending specs and --fail-on-pending is set")
|
|
suite.report.SuiteSucceeded = false
|
|
}
|
|
|
|
if suite.config.FailOnEmpty && specs.CountWithoutSkip() == 0 {
|
|
suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Detected no specs ran and --fail-on-empty is set")
|
|
suite.report.SuiteSucceeded = false
|
|
}
|
|
}
|
|
|
|
if ranBeforeSuite {
|
|
suite.runAfterSuiteCleanup(numSpecsThatWillBeRun)
|
|
}
|
|
|
|
interruptStatus := suite.interruptHandler.Status()
|
|
if interruptStatus.Interrupted() {
|
|
suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, interruptStatus.Cause.String())
|
|
suite.report.SuiteSucceeded = false
|
|
}
|
|
suite.report.EndTime = time.Now()
|
|
suite.report.RunTime = suite.report.EndTime.Sub(suite.report.StartTime)
|
|
if !suite.deadline.IsZero() && suite.report.EndTime.After(suite.deadline) {
|
|
suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Suite Timeout Elapsed")
|
|
suite.report.SuiteSucceeded = false
|
|
}
|
|
|
|
suite.runReportSuiteNodesIfNeedBe(types.NodeTypeReportAfterSuite)
|
|
suite.reporter.SuiteDidEnd(suite.report)
|
|
if suite.isRunningInParallel() {
|
|
suite.client.PostSuiteDidEnd(suite.report)
|
|
}
|
|
|
|
return suite.report.SuiteSucceeded
|
|
}
|
|
|
|
func (suite *Suite) runBeforeSuite(numSpecsThatWillBeRun int) {
|
|
beforeSuiteNode := suite.suiteNodes.FirstNodeWithType(types.NodeTypeBeforeSuite | types.NodeTypeSynchronizedBeforeSuite)
|
|
if !beforeSuiteNode.IsZero() && numSpecsThatWillBeRun > 0 {
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecReport = types.SpecReport{
|
|
LeafNodeType: beforeSuiteNode.NodeType,
|
|
LeafNodeLocation: beforeSuiteNode.CodeLocation,
|
|
ParallelProcess: suite.config.ParallelProcess,
|
|
RunningInParallel: suite.isRunningInParallel(),
|
|
}
|
|
suite.selectiveLock.Unlock()
|
|
|
|
suite.reporter.WillRun(suite.currentSpecReport)
|
|
suite.runSuiteNode(beforeSuiteNode)
|
|
if suite.currentSpecReport.State.Is(types.SpecStateSkipped) {
|
|
suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Suite skipped in BeforeSuite")
|
|
suite.skipAll = true
|
|
}
|
|
suite.processCurrentSpecReport()
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) runAfterSuiteCleanup(numSpecsThatWillBeRun int) {
|
|
afterSuiteNode := suite.suiteNodes.FirstNodeWithType(types.NodeTypeAfterSuite | types.NodeTypeSynchronizedAfterSuite)
|
|
if !afterSuiteNode.IsZero() && numSpecsThatWillBeRun > 0 {
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecReport = types.SpecReport{
|
|
LeafNodeType: afterSuiteNode.NodeType,
|
|
LeafNodeLocation: afterSuiteNode.CodeLocation,
|
|
ParallelProcess: suite.config.ParallelProcess,
|
|
RunningInParallel: suite.isRunningInParallel(),
|
|
}
|
|
suite.selectiveLock.Unlock()
|
|
|
|
suite.reporter.WillRun(suite.currentSpecReport)
|
|
suite.runSuiteNode(afterSuiteNode)
|
|
suite.processCurrentSpecReport()
|
|
}
|
|
|
|
afterSuiteCleanup := suite.cleanupNodes.WithType(types.NodeTypeCleanupAfterSuite).Reverse()
|
|
if len(afterSuiteCleanup) > 0 {
|
|
for _, cleanupNode := range afterSuiteCleanup {
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecReport = types.SpecReport{
|
|
LeafNodeType: cleanupNode.NodeType,
|
|
LeafNodeLocation: cleanupNode.CodeLocation,
|
|
ParallelProcess: suite.config.ParallelProcess,
|
|
RunningInParallel: suite.isRunningInParallel(),
|
|
}
|
|
suite.selectiveLock.Unlock()
|
|
|
|
suite.reporter.WillRun(suite.currentSpecReport)
|
|
suite.runSuiteNode(cleanupNode)
|
|
suite.processCurrentSpecReport()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) reportEach(spec Spec, nodeType types.NodeType) {
|
|
nodes := spec.Nodes.WithType(nodeType)
|
|
if nodeType == types.NodeTypeReportAfterEach {
|
|
nodes = nodes.SortedByDescendingNestingLevel()
|
|
}
|
|
if nodeType == types.NodeTypeReportBeforeEach {
|
|
nodes = nodes.SortedByAscendingNestingLevel()
|
|
}
|
|
if len(nodes) == 0 {
|
|
return
|
|
}
|
|
|
|
for i := range nodes {
|
|
suite.writer.Truncate()
|
|
suite.outputInterceptor.StartInterceptingOutput()
|
|
report := suite.currentSpecReport
|
|
nodes[i].Body = func(ctx SpecContext) {
|
|
nodes[i].ReportEachBody(ctx, report)
|
|
}
|
|
state, failure := suite.runNode(nodes[i], time.Time{}, spec.Nodes.BestTextFor(nodes[i]))
|
|
|
|
// If the spec is not in a failure state (i.e. it's Passed/Skipped/Pending) and the reporter has failed, override the state.
|
|
// Also, if the reporter is every aborted - always override the state to propagate the abort
|
|
if (!suite.currentSpecReport.State.Is(types.SpecStateFailureStates) && state.Is(types.SpecStateFailureStates)) || state.Is(types.SpecStateAborted) {
|
|
suite.currentSpecReport.State = state
|
|
suite.currentSpecReport.Failure = failure
|
|
}
|
|
suite.currentSpecReport.CapturedGinkgoWriterOutput += string(suite.writer.Bytes())
|
|
suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput()
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) runSuiteNode(node Node) {
|
|
if suite.config.DryRun {
|
|
suite.currentSpecReport.State = types.SpecStatePassed
|
|
return
|
|
}
|
|
|
|
suite.writer.Truncate()
|
|
suite.outputInterceptor.StartInterceptingOutput()
|
|
suite.currentSpecReport.StartTime = time.Now()
|
|
|
|
var err error
|
|
switch node.NodeType {
|
|
case types.NodeTypeBeforeSuite, types.NodeTypeAfterSuite:
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "")
|
|
case types.NodeTypeCleanupAfterSuite:
|
|
if suite.config.ParallelTotal > 1 && suite.config.ParallelProcess == 1 {
|
|
err = suite.client.BlockUntilNonprimaryProcsHaveFinished()
|
|
}
|
|
if err == nil {
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "")
|
|
}
|
|
case types.NodeTypeSynchronizedBeforeSuite:
|
|
var data []byte
|
|
var runAllProcs bool
|
|
if suite.config.ParallelProcess == 1 {
|
|
if suite.config.ParallelTotal > 1 {
|
|
suite.outputInterceptor.StopInterceptingAndReturnOutput()
|
|
suite.outputInterceptor.StartInterceptingOutputAndForwardTo(suite.client)
|
|
}
|
|
node.Body = func(c SpecContext) { data = node.SynchronizedBeforeSuiteProc1Body(c) }
|
|
node.HasContext = node.SynchronizedBeforeSuiteProc1BodyHasContext
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "")
|
|
if suite.config.ParallelTotal > 1 {
|
|
suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput()
|
|
suite.outputInterceptor.StartInterceptingOutput()
|
|
if suite.currentSpecReport.State.Is(types.SpecStatePassed) {
|
|
err = suite.client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, data)
|
|
} else {
|
|
err = suite.client.PostSynchronizedBeforeSuiteCompleted(suite.currentSpecReport.State, nil)
|
|
}
|
|
}
|
|
runAllProcs = suite.currentSpecReport.State.Is(types.SpecStatePassed) && err == nil
|
|
} else {
|
|
var proc1State types.SpecState
|
|
proc1State, data, err = suite.client.BlockUntilSynchronizedBeforeSuiteData()
|
|
switch proc1State {
|
|
case types.SpecStatePassed:
|
|
runAllProcs = true
|
|
case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateTimedout:
|
|
err = types.GinkgoErrors.SynchronizedBeforeSuiteFailedOnProc1()
|
|
case types.SpecStateInterrupted, types.SpecStateAborted, types.SpecStateSkipped:
|
|
suite.currentSpecReport.State = proc1State
|
|
}
|
|
}
|
|
if runAllProcs {
|
|
node.Body = func(c SpecContext) { node.SynchronizedBeforeSuiteAllProcsBody(c, data) }
|
|
node.HasContext = node.SynchronizedBeforeSuiteAllProcsBodyHasContext
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "")
|
|
}
|
|
case types.NodeTypeSynchronizedAfterSuite:
|
|
node.Body = node.SynchronizedAfterSuiteAllProcsBody
|
|
node.HasContext = node.SynchronizedAfterSuiteAllProcsBodyHasContext
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "")
|
|
if suite.config.ParallelProcess == 1 {
|
|
if suite.config.ParallelTotal > 1 {
|
|
err = suite.client.BlockUntilNonprimaryProcsHaveFinished()
|
|
}
|
|
if err == nil {
|
|
if suite.config.ParallelTotal > 1 {
|
|
suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput()
|
|
suite.outputInterceptor.StartInterceptingOutputAndForwardTo(suite.client)
|
|
}
|
|
|
|
node.Body = node.SynchronizedAfterSuiteProc1Body
|
|
node.HasContext = node.SynchronizedAfterSuiteProc1BodyHasContext
|
|
state, failure := suite.runNode(node, time.Time{}, "")
|
|
if suite.currentSpecReport.State.Is(types.SpecStatePassed) {
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = state, failure
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err != nil && !suite.currentSpecReport.State.Is(types.SpecStateFailureStates) {
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = types.SpecStateFailed, suite.failureForLeafNodeWithMessage(node, err.Error())
|
|
suite.reporter.EmitFailure(suite.currentSpecReport.State, suite.currentSpecReport.Failure)
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
func (suite *Suite) runReportSuiteNodesIfNeedBe(nodeType types.NodeType) {
|
|
nodes := suite.suiteNodes.WithType(nodeType)
|
|
// only run ReportAfterSuite on proc 1
|
|
if nodeType.Is(types.NodeTypeReportAfterSuite) && suite.config.ParallelProcess != 1 {
|
|
return
|
|
}
|
|
// if we're running ReportBeforeSuite on proc > 1 - we should wait until proc 1 has completed
|
|
if nodeType.Is(types.NodeTypeReportBeforeSuite) && suite.config.ParallelProcess != 1 && len(nodes) > 0 {
|
|
state, err := suite.client.BlockUntilReportBeforeSuiteCompleted()
|
|
if err != nil || state.Is(types.SpecStateFailed) {
|
|
suite.report.SuiteSucceeded = false
|
|
}
|
|
return
|
|
}
|
|
|
|
for _, node := range nodes {
|
|
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.runReportSuiteNode(node, suite.report)
|
|
suite.processCurrentSpecReport()
|
|
}
|
|
|
|
// if we're running ReportBeforeSuite and we're running in parallel - we shuld tell the other procs that we're done
|
|
if nodeType.Is(types.NodeTypeReportBeforeSuite) && suite.isRunningInParallel() && len(nodes) > 0 {
|
|
if suite.report.SuiteSucceeded {
|
|
suite.client.PostReportBeforeSuiteCompleted(types.SpecStatePassed)
|
|
} else {
|
|
suite.client.PostReportBeforeSuiteCompleted(types.SpecStateFailed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (suite *Suite) runReportSuiteNode(node Node, report types.Report) {
|
|
suite.writer.Truncate()
|
|
suite.outputInterceptor.StartInterceptingOutput()
|
|
suite.currentSpecReport.StartTime = time.Now()
|
|
|
|
// if we're running a ReportAfterSuite in parallel (on proc 1) we (a) wait until other procs have exited and
|
|
// (b) always fetch the latest report as prior ReportAfterSuites will contribute to it
|
|
if node.NodeType.Is(types.NodeTypeReportAfterSuite) && suite.isRunningInParallel() {
|
|
aggregatedReport, err := suite.client.BlockUntilAggregatedNonprimaryProcsReport()
|
|
if err != nil {
|
|
suite.currentSpecReport.State, suite.currentSpecReport.Failure = types.SpecStateFailed, suite.failureForLeafNodeWithMessage(node, err.Error())
|
|
suite.reporter.EmitFailure(suite.currentSpecReport.State, suite.currentSpecReport.Failure)
|
|
return
|
|
}
|
|
report = report.Add(aggregatedReport)
|
|
}
|
|
|
|
node.Body = func(ctx SpecContext) { node.ReportSuiteBody(ctx, 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()
|
|
}
|
|
|
|
func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (types.SpecState, types.Failure) {
|
|
if node.NodeType.Is(types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll | types.NodeTypeCleanupAfterSuite) {
|
|
suite.cleanupNodes = suite.cleanupNodes.WithoutNode(node)
|
|
}
|
|
|
|
interruptStatus := suite.interruptHandler.Status()
|
|
if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut {
|
|
return types.SpecStateSkipped, types.Failure{}
|
|
}
|
|
if interruptStatus.Level == interrupt_handler.InterruptLevelReportOnly && !node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt) {
|
|
return types.SpecStateSkipped, types.Failure{}
|
|
}
|
|
if interruptStatus.Level == interrupt_handler.InterruptLevelCleanupAndReport && !node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt|types.NodeTypesAllowedDuringCleanupInterrupt) {
|
|
return types.SpecStateSkipped, types.Failure{}
|
|
}
|
|
|
|
suite.selectiveLock.Lock()
|
|
suite.currentNode = node
|
|
suite.currentNodeStartTime = time.Now()
|
|
suite.currentByStep = types.SpecEvent{}
|
|
suite.selectiveLock.Unlock()
|
|
defer func() {
|
|
suite.selectiveLock.Lock()
|
|
suite.currentNode = Node{}
|
|
suite.currentNodeStartTime = time.Time{}
|
|
suite.selectiveLock.Unlock()
|
|
}()
|
|
|
|
if text == "" {
|
|
text = "TOP-LEVEL"
|
|
}
|
|
event := suite.handleSpecEvent(types.SpecEvent{
|
|
SpecEventType: types.SpecEventNodeStart,
|
|
NodeType: node.NodeType,
|
|
Message: text,
|
|
CodeLocation: node.CodeLocation,
|
|
})
|
|
defer func() {
|
|
suite.handleSpecEventEnd(types.SpecEventNodeEnd, event)
|
|
}()
|
|
|
|
var failure types.Failure
|
|
failure.FailureNodeType, failure.FailureNodeLocation = node.NodeType, node.CodeLocation
|
|
if node.NodeType.Is(types.NodeTypeIt) || node.NodeType.Is(types.NodeTypesForSuiteLevelNodes) {
|
|
failure.FailureNodeContext = types.FailureNodeIsLeafNode
|
|
} else if node.NestingLevel <= 0 {
|
|
failure.FailureNodeContext = types.FailureNodeAtTopLevel
|
|
} else {
|
|
failure.FailureNodeContext, failure.FailureNodeContainerIndex = types.FailureNodeInContainer, node.NestingLevel-1
|
|
}
|
|
var outcome types.SpecState
|
|
|
|
gracePeriod := suite.config.GracePeriod
|
|
if node.GracePeriod >= 0 {
|
|
gracePeriod = node.GracePeriod
|
|
}
|
|
|
|
now := time.Now()
|
|
deadline := suite.deadline
|
|
timeoutInPlay := "suite"
|
|
if deadline.IsZero() || (!specDeadline.IsZero() && specDeadline.Before(deadline)) {
|
|
deadline = specDeadline
|
|
timeoutInPlay = "spec"
|
|
}
|
|
if node.NodeTimeout > 0 && (deadline.IsZero() || deadline.Sub(now) > node.NodeTimeout) {
|
|
deadline = now.Add(node.NodeTimeout)
|
|
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
|
|
if node.NodeTimeout > 0 {
|
|
deadline = now.Add(node.NodeTimeout)
|
|
timeoutInPlay = "node"
|
|
} else {
|
|
deadline = now.Add(gracePeriod)
|
|
timeoutInPlay = "grace period"
|
|
}
|
|
}
|
|
|
|
if !node.HasContext {
|
|
// this maps onto the pre-context behavior:
|
|
// - an interrupted node exits immediately. with this, context-less nodes that are in a spec with a SpecTimeout and/or are interrupted by other means will simply exit immediately after the timeout/interrupt
|
|
// - clean up nodes have up to GracePeriod (formerly hard-coded at 30s) to complete before they are interrupted
|
|
gracePeriod = 0
|
|
}
|
|
|
|
sc := NewSpecContext(suite)
|
|
defer sc.cancel(fmt.Errorf("spec has finished"))
|
|
|
|
suite.selectiveLock.Lock()
|
|
suite.currentSpecContext = sc
|
|
suite.selectiveLock.Unlock()
|
|
|
|
var deadlineChannel <-chan time.Time
|
|
if !deadline.IsZero() {
|
|
deadlineChannel = time.After(deadline.Sub(now))
|
|
}
|
|
var gracePeriodChannel <-chan time.Time
|
|
|
|
outcomeC := make(chan types.SpecState)
|
|
failureC := make(chan types.Failure)
|
|
|
|
go func() {
|
|
finished := false
|
|
defer func() {
|
|
if e := recover(); e != nil || !finished {
|
|
suite.failer.Panic(types.NewCodeLocationWithStackTrace(2), e)
|
|
}
|
|
|
|
outcomeFromRun, failureFromRun := suite.failer.Drain()
|
|
failureFromRun.TimelineLocation = suite.generateTimelineLocation()
|
|
outcomeC <- outcomeFromRun
|
|
failureC <- failureFromRun
|
|
}()
|
|
|
|
node.Body(sc)
|
|
finished = true
|
|
}()
|
|
|
|
// progress polling timer and channel
|
|
var emitProgressNow <-chan time.Time
|
|
var progressPoller *time.Timer
|
|
var pollProgressAfter, pollProgressInterval = suite.config.PollProgressAfter, suite.config.PollProgressInterval
|
|
if node.PollProgressAfter >= 0 {
|
|
pollProgressAfter = node.PollProgressAfter
|
|
}
|
|
if node.PollProgressInterval >= 0 {
|
|
pollProgressInterval = node.PollProgressInterval
|
|
}
|
|
if pollProgressAfter > 0 {
|
|
progressPoller = time.NewTimer(pollProgressAfter)
|
|
emitProgressNow = progressPoller.C
|
|
defer progressPoller.Stop()
|
|
}
|
|
|
|
// now we wait for an outcome, an interrupt, a timeout, or a progress poll
|
|
for {
|
|
select {
|
|
case outcomeFromRun := <-outcomeC:
|
|
failureFromRun := <-failureC
|
|
if outcome.Is(types.SpecStateInterrupted | types.SpecStateTimedout) {
|
|
// we've already been interrupted/timed out. we just managed to actually exit
|
|
// before the grace period elapsed
|
|
// if we have a failure message we attach it as an additional failure
|
|
if outcomeFromRun != types.SpecStatePassed {
|
|
additionalFailure := types.AdditionalFailure{
|
|
State: outcomeFromRun,
|
|
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
|
|
additionalFailure.Failure.Location, additionalFailure.Failure.ForwardedPanic, additionalFailure.Failure.TimelineLocation = failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation
|
|
additionalFailure.Failure.ProgressReport = types.ProgressReport{}
|
|
if outcome == types.SpecStateTimedout {
|
|
additionalFailure.Failure.Message = fmt.Sprintf("A %s timeout occurred and then the following failure was recorded in the timedout node before it exited:\n%s", timeoutInPlay, failureFromRun.Message)
|
|
} else {
|
|
additionalFailure.Failure.Message = fmt.Sprintf("An interrupt occurred and then the following failure was recorded in the interrupted node before it exited:\n%s", failureFromRun.Message)
|
|
}
|
|
suite.reporter.EmitFailure(additionalFailure.State, additionalFailure.Failure)
|
|
failure.AdditionalFailure = &additionalFailure
|
|
}
|
|
return outcome, failure
|
|
}
|
|
if outcomeFromRun.Is(types.SpecStatePassed) {
|
|
return outcomeFromRun, types.Failure{}
|
|
} else {
|
|
failure.Message, failure.Location, failure.ForwardedPanic, failure.TimelineLocation = failureFromRun.Message, failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation
|
|
suite.reporter.EmitFailure(outcomeFromRun, failure)
|
|
return outcomeFromRun, failure
|
|
}
|
|
case <-gracePeriodChannel:
|
|
if node.HasContext && outcome.Is(types.SpecStateTimedout) {
|
|
report := suite.generateProgressReport(false)
|
|
report.Message = "{{bold}}{{orange}}A running node failed to exit in time{{/}}\nGinkgo is moving on but a node has timed out and failed to exit before its grace period elapsed. The node has now leaked and is running in the background.\nHere's a current progress report:"
|
|
suite.emitProgressReport(report)
|
|
}
|
|
return outcome, failure
|
|
case <-deadlineChannel:
|
|
// we're out of time - the outcome is a timeout and we capture the failure and progress report
|
|
outcome = types.SpecStateTimedout
|
|
failure.Message, failure.Location, failure.TimelineLocation = fmt.Sprintf("A %s timeout occurred", timeoutInPlay), node.CodeLocation, suite.generateTimelineLocation()
|
|
failure.ProgressReport = suite.generateProgressReport(false).WithoutCapturedGinkgoWriterOutput()
|
|
failure.ProgressReport.Message = fmt.Sprintf("{{bold}}This is the Progress Report generated when the %s timeout occurred:{{/}}", timeoutInPlay)
|
|
deadlineChannel = nil
|
|
suite.reporter.EmitFailure(outcome, failure)
|
|
|
|
// 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
|
|
gracePeriodChannel = time.After(gracePeriod)
|
|
case <-interruptStatus.Channel:
|
|
interruptStatus = suite.interruptHandler.Status()
|
|
// ignore interruption from other process if we are cleaning up or reporting
|
|
if interruptStatus.Cause == interrupt_handler.InterruptCauseAbortByOtherProcess &&
|
|
node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt|types.NodeTypesAllowedDuringCleanupInterrupt) {
|
|
continue
|
|
}
|
|
|
|
deadlineChannel = nil // don't worry about deadlines, time's up now
|
|
|
|
failureTimelineLocation := suite.generateTimelineLocation()
|
|
progressReport := suite.generateProgressReport(true)
|
|
|
|
if outcome == types.SpecStateInvalid {
|
|
outcome = types.SpecStateInterrupted
|
|
failure.Message, failure.Location, failure.TimelineLocation = interruptStatus.Message(), node.CodeLocation, failureTimelineLocation
|
|
if interruptStatus.ShouldIncludeProgressReport() {
|
|
failure.ProgressReport = progressReport.WithoutCapturedGinkgoWriterOutput()
|
|
failure.ProgressReport.Message = "{{bold}}This is the Progress Report generated when the interrupt was received:{{/}}"
|
|
}
|
|
suite.reporter.EmitFailure(outcome, failure)
|
|
}
|
|
|
|
progressReport = progressReport.WithoutOtherGoroutines()
|
|
sc.cancel(fmt.Errorf(interruptStatus.Message()))
|
|
|
|
if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut {
|
|
if interruptStatus.ShouldIncludeProgressReport() {
|
|
progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\n{{bold}}{{red}}Final interrupt received{{/}}; Ginkgo will not run any cleanup or reporting nodes and will terminate as soon as possible.\nHere's a current progress report:", interruptStatus.Message())
|
|
suite.emitProgressReport(progressReport)
|
|
}
|
|
return outcome, failure
|
|
}
|
|
if interruptStatus.ShouldIncludeProgressReport() {
|
|
if interruptStatus.Level == interrupt_handler.InterruptLevelCleanupAndReport {
|
|
progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\nFirst interrupt received; Ginkgo will run any cleanup and reporting nodes but will skip all remaining specs. {{bold}}Interrupt again to skip cleanup{{/}}.\nHere's a current progress report:", interruptStatus.Message())
|
|
} else if interruptStatus.Level == interrupt_handler.InterruptLevelReportOnly {
|
|
progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\nSecond interrupt received; Ginkgo will run any reporting nodes but will skip all remaining specs and cleanup nodes. {{bold}}Interrupt again to bail immediately{{/}}.\nHere's a current progress report:", interruptStatus.Message())
|
|
}
|
|
suite.emitProgressReport(progressReport)
|
|
}
|
|
|
|
if gracePeriodChannel == nil {
|
|
// we haven't given grace yet... so let's
|
|
gracePeriodChannel = time.After(gracePeriod)
|
|
} else {
|
|
// we've already given grace. time's up. now.
|
|
return outcome, failure
|
|
}
|
|
case <-emitProgressNow:
|
|
report := suite.generateProgressReport(false)
|
|
report.Message = "{{bold}}Automatically polling progress:{{/}}"
|
|
suite.emitProgressReport(report)
|
|
if pollProgressInterval > 0 {
|
|
progressPoller.Reset(pollProgressInterval)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: search for usages and consider if reporter.EmitFailure() is necessary
|
|
func (suite *Suite) failureForLeafNodeWithMessage(node Node, message string) types.Failure {
|
|
return types.Failure{
|
|
Message: message,
|
|
Location: node.CodeLocation,
|
|
TimelineLocation: suite.generateTimelineLocation(),
|
|
FailureNodeContext: types.FailureNodeIsLeafNode,
|
|
FailureNodeType: node.NodeType,
|
|
FailureNodeLocation: node.CodeLocation,
|
|
}
|
|
}
|
|
|
|
func max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|