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