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() signalRegistered := make(chan struct{}) go runner.registerForInterrupts(signalRegistered) <-signalRegistered 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(signalRegistered chan struct{}) { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) close(signalRegistered) <-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 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, } }