409 lines
10 KiB
Go
409 lines
10 KiB
Go
package specrunner
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/onsi/ginkgo/internal/spec_iterator"
|
|
|
|
"github.com/onsi/ginkgo/config"
|
|
"github.com/onsi/ginkgo/internal/leafnodes"
|
|
"github.com/onsi/ginkgo/internal/spec"
|
|
Writer "github.com/onsi/ginkgo/internal/writer"
|
|
"github.com/onsi/ginkgo/reporters"
|
|
"github.com/onsi/ginkgo/types"
|
|
|
|
"time"
|
|
)
|
|
|
|
type SpecRunner struct {
|
|
description string
|
|
beforeSuiteNode leafnodes.SuiteNode
|
|
iterator spec_iterator.SpecIterator
|
|
afterSuiteNode leafnodes.SuiteNode
|
|
reporters []reporters.Reporter
|
|
startTime time.Time
|
|
suiteID string
|
|
runningSpec *spec.Spec
|
|
writer Writer.WriterInterface
|
|
config config.GinkgoConfigType
|
|
interrupted bool
|
|
processedSpecs []*spec.Spec
|
|
lock *sync.Mutex
|
|
}
|
|
|
|
func New(description string, beforeSuiteNode leafnodes.SuiteNode, iterator spec_iterator.SpecIterator, afterSuiteNode leafnodes.SuiteNode, reporters []reporters.Reporter, writer Writer.WriterInterface, config config.GinkgoConfigType) *SpecRunner {
|
|
return &SpecRunner{
|
|
description: description,
|
|
beforeSuiteNode: beforeSuiteNode,
|
|
iterator: iterator,
|
|
afterSuiteNode: afterSuiteNode,
|
|
reporters: reporters,
|
|
writer: writer,
|
|
config: config,
|
|
suiteID: randomID(),
|
|
lock: &sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) Run() bool {
|
|
if runner.config.DryRun {
|
|
runner.performDryRun()
|
|
return true
|
|
}
|
|
|
|
runner.reportSuiteWillBegin()
|
|
go runner.registerForInterrupts()
|
|
|
|
suitePassed := runner.runBeforeSuite()
|
|
|
|
if suitePassed {
|
|
suitePassed = runner.runSpecs()
|
|
}
|
|
|
|
runner.blockForeverIfInterrupted()
|
|
|
|
suitePassed = runner.runAfterSuite() && suitePassed
|
|
|
|
runner.reportSuiteDidEnd(suitePassed)
|
|
|
|
return suitePassed
|
|
}
|
|
|
|
func (runner *SpecRunner) performDryRun() {
|
|
runner.reportSuiteWillBegin()
|
|
|
|
if runner.beforeSuiteNode != nil {
|
|
summary := runner.beforeSuiteNode.Summary()
|
|
summary.State = types.SpecStatePassed
|
|
runner.reportBeforeSuite(summary)
|
|
}
|
|
|
|
for {
|
|
spec, err := runner.iterator.Next()
|
|
if err == spec_iterator.ErrClosed {
|
|
break
|
|
}
|
|
if err != nil {
|
|
fmt.Println("failed to iterate over tests:\n" + err.Error())
|
|
break
|
|
}
|
|
|
|
runner.processedSpecs = append(runner.processedSpecs, spec)
|
|
|
|
summary := spec.Summary(runner.suiteID)
|
|
runner.reportSpecWillRun(summary)
|
|
if summary.State == types.SpecStateInvalid {
|
|
summary.State = types.SpecStatePassed
|
|
}
|
|
runner.reportSpecDidComplete(summary, false)
|
|
}
|
|
|
|
if runner.afterSuiteNode != nil {
|
|
summary := runner.afterSuiteNode.Summary()
|
|
summary.State = types.SpecStatePassed
|
|
runner.reportAfterSuite(summary)
|
|
}
|
|
|
|
runner.reportSuiteDidEnd(true)
|
|
}
|
|
|
|
func (runner *SpecRunner) runBeforeSuite() bool {
|
|
if runner.beforeSuiteNode == nil || runner.wasInterrupted() {
|
|
return true
|
|
}
|
|
|
|
runner.writer.Truncate()
|
|
conf := runner.config
|
|
passed := runner.beforeSuiteNode.Run(conf.ParallelNode, conf.ParallelTotal, conf.SyncHost)
|
|
if !passed {
|
|
runner.writer.DumpOut()
|
|
}
|
|
runner.reportBeforeSuite(runner.beforeSuiteNode.Summary())
|
|
return passed
|
|
}
|
|
|
|
func (runner *SpecRunner) runAfterSuite() bool {
|
|
if runner.afterSuiteNode == nil {
|
|
return true
|
|
}
|
|
|
|
runner.writer.Truncate()
|
|
conf := runner.config
|
|
passed := runner.afterSuiteNode.Run(conf.ParallelNode, conf.ParallelTotal, conf.SyncHost)
|
|
if !passed {
|
|
runner.writer.DumpOut()
|
|
}
|
|
runner.reportAfterSuite(runner.afterSuiteNode.Summary())
|
|
return passed
|
|
}
|
|
|
|
func (runner *SpecRunner) runSpecs() bool {
|
|
suiteFailed := false
|
|
skipRemainingSpecs := false
|
|
for {
|
|
spec, err := runner.iterator.Next()
|
|
if err == spec_iterator.ErrClosed {
|
|
break
|
|
}
|
|
if err != nil {
|
|
fmt.Println("failed to iterate over tests:\n" + err.Error())
|
|
suiteFailed = true
|
|
break
|
|
}
|
|
|
|
runner.processedSpecs = append(runner.processedSpecs, spec)
|
|
|
|
if runner.wasInterrupted() {
|
|
break
|
|
}
|
|
if skipRemainingSpecs {
|
|
spec.Skip()
|
|
}
|
|
|
|
if !spec.Skipped() && !spec.Pending() {
|
|
if passed := runner.runSpec(spec); !passed {
|
|
suiteFailed = true
|
|
}
|
|
} else if spec.Pending() && runner.config.FailOnPending {
|
|
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
|
|
suiteFailed = true
|
|
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
|
|
} else {
|
|
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
|
|
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
|
|
}
|
|
|
|
if spec.Failed() && runner.config.FailFast {
|
|
skipRemainingSpecs = true
|
|
}
|
|
}
|
|
|
|
return !suiteFailed
|
|
}
|
|
|
|
func (runner *SpecRunner) runSpec(spec *spec.Spec) (passed bool) {
|
|
maxAttempts := 1
|
|
if runner.config.FlakeAttempts > 0 {
|
|
// uninitialized configs count as 1
|
|
maxAttempts = runner.config.FlakeAttempts
|
|
}
|
|
|
|
for i := 0; i < maxAttempts; i++ {
|
|
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
|
|
runner.runningSpec = spec
|
|
spec.Run(runner.writer)
|
|
runner.runningSpec = nil
|
|
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
|
|
if !spec.Failed() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (runner *SpecRunner) CurrentSpecSummary() (*types.SpecSummary, bool) {
|
|
if runner.runningSpec == nil {
|
|
return nil, false
|
|
}
|
|
|
|
return runner.runningSpec.Summary(runner.suiteID), true
|
|
}
|
|
|
|
func (runner *SpecRunner) registerForInterrupts() {
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
|
|
<-c
|
|
signal.Stop(c)
|
|
runner.markInterrupted()
|
|
go runner.registerForHardInterrupts()
|
|
runner.writer.DumpOutWithHeader(`
|
|
Received interrupt. Emitting contents of GinkgoWriter...
|
|
---------------------------------------------------------
|
|
`)
|
|
if runner.afterSuiteNode != nil {
|
|
fmt.Fprint(os.Stderr, `
|
|
---------------------------------------------------------
|
|
Received interrupt. Running AfterSuite...
|
|
^C again to terminate immediately
|
|
`)
|
|
runner.runAfterSuite()
|
|
}
|
|
runner.reportSuiteDidEnd(false)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func (runner *SpecRunner) registerForHardInterrupts() {
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
|
|
<-c
|
|
fmt.Fprintln(os.Stderr, "\nReceived second interrupt. Shutting down.")
|
|
os.Exit(1)
|
|
}
|
|
|
|
func (runner *SpecRunner) blockForeverIfInterrupted() {
|
|
runner.lock.Lock()
|
|
interrupted := runner.interrupted
|
|
runner.lock.Unlock()
|
|
|
|
if interrupted {
|
|
select {}
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) markInterrupted() {
|
|
runner.lock.Lock()
|
|
defer runner.lock.Unlock()
|
|
runner.interrupted = true
|
|
}
|
|
|
|
func (runner *SpecRunner) wasInterrupted() bool {
|
|
runner.lock.Lock()
|
|
defer runner.lock.Unlock()
|
|
return runner.interrupted
|
|
}
|
|
|
|
func (runner *SpecRunner) reportSuiteWillBegin() {
|
|
runner.startTime = time.Now()
|
|
summary := runner.suiteWillBeginSummary()
|
|
for _, reporter := range runner.reporters {
|
|
reporter.SpecSuiteWillBegin(runner.config, summary)
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) reportBeforeSuite(summary *types.SetupSummary) {
|
|
for _, reporter := range runner.reporters {
|
|
reporter.BeforeSuiteDidRun(summary)
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) reportAfterSuite(summary *types.SetupSummary) {
|
|
for _, reporter := range runner.reporters {
|
|
reporter.AfterSuiteDidRun(summary)
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) reportSpecWillRun(summary *types.SpecSummary) {
|
|
runner.writer.Truncate()
|
|
|
|
for _, reporter := range runner.reporters {
|
|
reporter.SpecWillRun(summary)
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) reportSpecDidComplete(summary *types.SpecSummary, failed bool) {
|
|
if failed && len(summary.CapturedOutput) == 0 {
|
|
summary.CapturedOutput = string(runner.writer.Bytes())
|
|
}
|
|
for i := len(runner.reporters) - 1; i >= 1; i-- {
|
|
runner.reporters[i].SpecDidComplete(summary)
|
|
}
|
|
|
|
if failed {
|
|
runner.writer.DumpOut()
|
|
}
|
|
|
|
runner.reporters[0].SpecDidComplete(summary)
|
|
}
|
|
|
|
func (runner *SpecRunner) reportSuiteDidEnd(success bool) {
|
|
summary := runner.suiteDidEndSummary(success)
|
|
summary.RunTime = time.Since(runner.startTime)
|
|
for _, reporter := range runner.reporters {
|
|
reporter.SpecSuiteDidEnd(summary)
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) countSpecsThatRanSatisfying(filter func(ex *spec.Spec) bool) (count int) {
|
|
count = 0
|
|
|
|
for _, spec := range runner.processedSpecs {
|
|
if filter(spec) {
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func (runner *SpecRunner) suiteDidEndSummary(success bool) *types.SuiteSummary {
|
|
numberOfSpecsThatWillBeRun := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
|
|
return !ex.Skipped() && !ex.Pending()
|
|
})
|
|
|
|
numberOfPendingSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
|
|
return ex.Pending()
|
|
})
|
|
|
|
numberOfSkippedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
|
|
return ex.Skipped()
|
|
})
|
|
|
|
numberOfPassedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
|
|
return ex.Passed()
|
|
})
|
|
|
|
numberOfFlakedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
|
|
return ex.Flaked()
|
|
})
|
|
|
|
numberOfFailedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
|
|
return ex.Failed()
|
|
})
|
|
|
|
if runner.beforeSuiteNode != nil && !runner.beforeSuiteNode.Passed() && !runner.config.DryRun {
|
|
var known bool
|
|
numberOfSpecsThatWillBeRun, known = runner.iterator.NumberOfSpecsThatWillBeRunIfKnown()
|
|
if !known {
|
|
numberOfSpecsThatWillBeRun = runner.iterator.NumberOfSpecsPriorToIteration()
|
|
}
|
|
numberOfFailedSpecs = numberOfSpecsThatWillBeRun
|
|
}
|
|
|
|
return &types.SuiteSummary{
|
|
SuiteDescription: runner.description,
|
|
SuiteSucceeded: success,
|
|
SuiteID: runner.suiteID,
|
|
|
|
NumberOfSpecsBeforeParallelization: runner.iterator.NumberOfSpecsPriorToIteration(),
|
|
NumberOfTotalSpecs: len(runner.processedSpecs),
|
|
NumberOfSpecsThatWillBeRun: numberOfSpecsThatWillBeRun,
|
|
NumberOfPendingSpecs: numberOfPendingSpecs,
|
|
NumberOfSkippedSpecs: numberOfSkippedSpecs,
|
|
NumberOfPassedSpecs: numberOfPassedSpecs,
|
|
NumberOfFailedSpecs: numberOfFailedSpecs,
|
|
NumberOfFlakedSpecs: numberOfFlakedSpecs,
|
|
}
|
|
}
|
|
|
|
func (runner *SpecRunner) suiteWillBeginSummary() *types.SuiteSummary {
|
|
numTotal, known := runner.iterator.NumberOfSpecsToProcessIfKnown()
|
|
if !known {
|
|
numTotal = -1
|
|
}
|
|
|
|
numToRun, known := runner.iterator.NumberOfSpecsThatWillBeRunIfKnown()
|
|
if !known {
|
|
numToRun = -1
|
|
}
|
|
|
|
return &types.SuiteSummary{
|
|
SuiteDescription: runner.description,
|
|
SuiteID: runner.suiteID,
|
|
|
|
NumberOfSpecsBeforeParallelization: runner.iterator.NumberOfSpecsPriorToIteration(),
|
|
NumberOfTotalSpecs: numTotal,
|
|
NumberOfSpecsThatWillBeRun: numToRun,
|
|
NumberOfPendingSpecs: -1,
|
|
NumberOfSkippedSpecs: -1,
|
|
NumberOfPassedSpecs: -1,
|
|
NumberOfFailedSpecs: -1,
|
|
NumberOfFlakedSpecs: -1,
|
|
}
|
|
}
|