1/* 2 3Aggregator is a reporter used by the Ginkgo CLI to aggregate and present parallel test output 4coherently as tests complete. You shouldn't need to use this in your code. To run tests in parallel: 5 6 ginkgo -nodes=N 7 8where N is the number of nodes you desire. 9*/ 10package remote 11 12import ( 13 "time" 14 15 "github.com/onsi/ginkgo/config" 16 "github.com/onsi/ginkgo/reporters/stenographer" 17 "github.com/onsi/ginkgo/types" 18) 19 20type configAndSuite struct { 21 config config.GinkgoConfigType 22 summary *types.SuiteSummary 23} 24 25type Aggregator struct { 26 nodeCount int 27 config config.DefaultReporterConfigType 28 stenographer stenographer.Stenographer 29 result chan bool 30 31 suiteBeginnings chan configAndSuite 32 aggregatedSuiteBeginnings []configAndSuite 33 34 beforeSuites chan *types.SetupSummary 35 aggregatedBeforeSuites []*types.SetupSummary 36 37 afterSuites chan *types.SetupSummary 38 aggregatedAfterSuites []*types.SetupSummary 39 40 specCompletions chan *types.SpecSummary 41 completedSpecs []*types.SpecSummary 42 43 suiteEndings chan *types.SuiteSummary 44 aggregatedSuiteEndings []*types.SuiteSummary 45 specs []*types.SpecSummary 46 47 startTime time.Time 48} 49 50func NewAggregator(nodeCount int, result chan bool, config config.DefaultReporterConfigType, stenographer stenographer.Stenographer) *Aggregator { 51 aggregator := &Aggregator{ 52 nodeCount: nodeCount, 53 result: result, 54 config: config, 55 stenographer: stenographer, 56 57 suiteBeginnings: make(chan configAndSuite), 58 beforeSuites: make(chan *types.SetupSummary), 59 afterSuites: make(chan *types.SetupSummary), 60 specCompletions: make(chan *types.SpecSummary), 61 suiteEndings: make(chan *types.SuiteSummary), 62 } 63 64 go aggregator.mux() 65 66 return aggregator 67} 68 69func (aggregator *Aggregator) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) { 70 aggregator.suiteBeginnings <- configAndSuite{config, summary} 71} 72 73func (aggregator *Aggregator) BeforeSuiteDidRun(setupSummary *types.SetupSummary) { 74 aggregator.beforeSuites <- setupSummary 75} 76 77func (aggregator *Aggregator) AfterSuiteDidRun(setupSummary *types.SetupSummary) { 78 aggregator.afterSuites <- setupSummary 79} 80 81func (aggregator *Aggregator) SpecWillRun(specSummary *types.SpecSummary) { 82 //noop 83} 84 85func (aggregator *Aggregator) SpecDidComplete(specSummary *types.SpecSummary) { 86 aggregator.specCompletions <- specSummary 87} 88 89func (aggregator *Aggregator) SpecSuiteDidEnd(summary *types.SuiteSummary) { 90 aggregator.suiteEndings <- summary 91} 92 93func (aggregator *Aggregator) mux() { 94loop: 95 for { 96 select { 97 case configAndSuite := <-aggregator.suiteBeginnings: 98 aggregator.registerSuiteBeginning(configAndSuite) 99 case setupSummary := <-aggregator.beforeSuites: 100 aggregator.registerBeforeSuite(setupSummary) 101 case setupSummary := <-aggregator.afterSuites: 102 aggregator.registerAfterSuite(setupSummary) 103 case specSummary := <-aggregator.specCompletions: 104 aggregator.registerSpecCompletion(specSummary) 105 case suite := <-aggregator.suiteEndings: 106 finished, passed := aggregator.registerSuiteEnding(suite) 107 if finished { 108 aggregator.result <- passed 109 break loop 110 } 111 } 112 } 113} 114 115func (aggregator *Aggregator) registerSuiteBeginning(configAndSuite configAndSuite) { 116 aggregator.aggregatedSuiteBeginnings = append(aggregator.aggregatedSuiteBeginnings, configAndSuite) 117 118 if len(aggregator.aggregatedSuiteBeginnings) == 1 { 119 aggregator.startTime = time.Now() 120 } 121 122 if len(aggregator.aggregatedSuiteBeginnings) != aggregator.nodeCount { 123 return 124 } 125 126 aggregator.stenographer.AnnounceSuite(configAndSuite.summary.SuiteDescription, configAndSuite.config.RandomSeed, configAndSuite.config.RandomizeAllSpecs, aggregator.config.Succinct) 127 128 totalNumberOfSpecs := 0 129 if len(aggregator.aggregatedSuiteBeginnings) > 0 { 130 totalNumberOfSpecs = configAndSuite.summary.NumberOfSpecsBeforeParallelization 131 } 132 133 aggregator.stenographer.AnnounceTotalNumberOfSpecs(totalNumberOfSpecs, aggregator.config.Succinct) 134 aggregator.stenographer.AnnounceAggregatedParallelRun(aggregator.nodeCount, aggregator.config.Succinct) 135 aggregator.flushCompletedSpecs() 136} 137 138func (aggregator *Aggregator) registerBeforeSuite(setupSummary *types.SetupSummary) { 139 aggregator.aggregatedBeforeSuites = append(aggregator.aggregatedBeforeSuites, setupSummary) 140 aggregator.flushCompletedSpecs() 141} 142 143func (aggregator *Aggregator) registerAfterSuite(setupSummary *types.SetupSummary) { 144 aggregator.aggregatedAfterSuites = append(aggregator.aggregatedAfterSuites, setupSummary) 145 aggregator.flushCompletedSpecs() 146} 147 148func (aggregator *Aggregator) registerSpecCompletion(specSummary *types.SpecSummary) { 149 aggregator.completedSpecs = append(aggregator.completedSpecs, specSummary) 150 aggregator.specs = append(aggregator.specs, specSummary) 151 aggregator.flushCompletedSpecs() 152} 153 154func (aggregator *Aggregator) flushCompletedSpecs() { 155 if len(aggregator.aggregatedSuiteBeginnings) != aggregator.nodeCount { 156 return 157 } 158 159 for _, setupSummary := range aggregator.aggregatedBeforeSuites { 160 aggregator.announceBeforeSuite(setupSummary) 161 } 162 163 for _, specSummary := range aggregator.completedSpecs { 164 aggregator.announceSpec(specSummary) 165 } 166 167 for _, setupSummary := range aggregator.aggregatedAfterSuites { 168 aggregator.announceAfterSuite(setupSummary) 169 } 170 171 aggregator.aggregatedBeforeSuites = []*types.SetupSummary{} 172 aggregator.completedSpecs = []*types.SpecSummary{} 173 aggregator.aggregatedAfterSuites = []*types.SetupSummary{} 174} 175 176func (aggregator *Aggregator) announceBeforeSuite(setupSummary *types.SetupSummary) { 177 aggregator.stenographer.AnnounceCapturedOutput(setupSummary.CapturedOutput) 178 if setupSummary.State != types.SpecStatePassed { 179 aggregator.stenographer.AnnounceBeforeSuiteFailure(setupSummary, aggregator.config.Succinct, aggregator.config.FullTrace) 180 } 181} 182 183func (aggregator *Aggregator) announceAfterSuite(setupSummary *types.SetupSummary) { 184 aggregator.stenographer.AnnounceCapturedOutput(setupSummary.CapturedOutput) 185 if setupSummary.State != types.SpecStatePassed { 186 aggregator.stenographer.AnnounceAfterSuiteFailure(setupSummary, aggregator.config.Succinct, aggregator.config.FullTrace) 187 } 188} 189 190func (aggregator *Aggregator) announceSpec(specSummary *types.SpecSummary) { 191 if aggregator.config.Verbose && specSummary.State != types.SpecStatePending && specSummary.State != types.SpecStateSkipped { 192 aggregator.stenographer.AnnounceSpecWillRun(specSummary) 193 } 194 195 aggregator.stenographer.AnnounceCapturedOutput(specSummary.CapturedOutput) 196 197 switch specSummary.State { 198 case types.SpecStatePassed: 199 if specSummary.IsMeasurement { 200 aggregator.stenographer.AnnounceSuccesfulMeasurement(specSummary, aggregator.config.Succinct) 201 } else if specSummary.RunTime.Seconds() >= aggregator.config.SlowSpecThreshold { 202 aggregator.stenographer.AnnounceSuccesfulSlowSpec(specSummary, aggregator.config.Succinct) 203 } else { 204 aggregator.stenographer.AnnounceSuccesfulSpec(specSummary) 205 } 206 207 case types.SpecStatePending: 208 aggregator.stenographer.AnnouncePendingSpec(specSummary, aggregator.config.NoisyPendings && !aggregator.config.Succinct) 209 case types.SpecStateSkipped: 210 aggregator.stenographer.AnnounceSkippedSpec(specSummary, aggregator.config.Succinct || !aggregator.config.NoisySkippings, aggregator.config.FullTrace) 211 case types.SpecStateTimedOut: 212 aggregator.stenographer.AnnounceSpecTimedOut(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace) 213 case types.SpecStatePanicked: 214 aggregator.stenographer.AnnounceSpecPanicked(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace) 215 case types.SpecStateFailed: 216 aggregator.stenographer.AnnounceSpecFailed(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace) 217 } 218} 219 220func (aggregator *Aggregator) registerSuiteEnding(suite *types.SuiteSummary) (finished bool, passed bool) { 221 aggregator.aggregatedSuiteEndings = append(aggregator.aggregatedSuiteEndings, suite) 222 if len(aggregator.aggregatedSuiteEndings) < aggregator.nodeCount { 223 return false, false 224 } 225 226 aggregatedSuiteSummary := &types.SuiteSummary{} 227 aggregatedSuiteSummary.SuiteSucceeded = true 228 229 for _, suiteSummary := range aggregator.aggregatedSuiteEndings { 230 if !suiteSummary.SuiteSucceeded { 231 aggregatedSuiteSummary.SuiteSucceeded = false 232 } 233 234 aggregatedSuiteSummary.NumberOfSpecsThatWillBeRun += suiteSummary.NumberOfSpecsThatWillBeRun 235 aggregatedSuiteSummary.NumberOfTotalSpecs += suiteSummary.NumberOfTotalSpecs 236 aggregatedSuiteSummary.NumberOfPassedSpecs += suiteSummary.NumberOfPassedSpecs 237 aggregatedSuiteSummary.NumberOfFailedSpecs += suiteSummary.NumberOfFailedSpecs 238 aggregatedSuiteSummary.NumberOfPendingSpecs += suiteSummary.NumberOfPendingSpecs 239 aggregatedSuiteSummary.NumberOfSkippedSpecs += suiteSummary.NumberOfSkippedSpecs 240 aggregatedSuiteSummary.NumberOfFlakedSpecs += suiteSummary.NumberOfFlakedSpecs 241 } 242 243 aggregatedSuiteSummary.RunTime = time.Since(aggregator.startTime) 244 245 aggregator.stenographer.SummarizeFailures(aggregator.specs) 246 aggregator.stenographer.AnnounceSpecRunCompletion(aggregatedSuiteSummary, aggregator.config.Succinct) 247 248 return true, aggregatedSuiteSummary.SuiteSucceeded 249} 250