File: suite_runner.go

package info (click to toggle)
golang-ginkgo 1.2.0%2Bgit20161006.acfa16a-1
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 1,324 kB
  • ctags: 1,210
  • sloc: makefile: 12
file content (172 lines) | stat: -rw-r--r-- 5,362 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package main

import (
	"fmt"
	"runtime"
	"sync"

	"github.com/onsi/ginkgo/config"
	"github.com/onsi/ginkgo/ginkgo/interrupthandler"
	"github.com/onsi/ginkgo/ginkgo/testrunner"
	"github.com/onsi/ginkgo/ginkgo/testsuite"
)

type compilationInput struct {
	runner *testrunner.TestRunner
	result chan compilationOutput
}

type compilationOutput struct {
	runner *testrunner.TestRunner
	err    error
}

type SuiteRunner struct {
	notifier         *Notifier
	interruptHandler *interrupthandler.InterruptHandler
}

func NewSuiteRunner(notifier *Notifier, interruptHandler *interrupthandler.InterruptHandler) *SuiteRunner {
	return &SuiteRunner{
		notifier:         notifier,
		interruptHandler: interruptHandler,
	}
}

func (r *SuiteRunner) compileInParallel(runners []*testrunner.TestRunner, numCompilers int, willCompile func(suite testsuite.TestSuite)) chan compilationOutput {
	//we return this to the consumer, it will return each runner in order as it compiles
	compilationOutputs := make(chan compilationOutput, len(runners))

	//an array of channels - the nth runner's compilation output is sent to the nth channel in this array
	//we read from these channels in order to ensure we run the suites in order
	orderedCompilationOutputs := []chan compilationOutput{}
	for _ = range runners {
		orderedCompilationOutputs = append(orderedCompilationOutputs, make(chan compilationOutput, 1))
	}

	//we're going to spin up numCompilers compilers - they're going to run concurrently and will consume this channel
	//we prefill the channel then close it, this ensures we compile things in the correct order
	workPool := make(chan compilationInput, len(runners))
	for i, runner := range runners {
		workPool <- compilationInput{runner, orderedCompilationOutputs[i]}
	}
	close(workPool)

	//pick a reasonable numCompilers
	if numCompilers == 0 {
		numCompilers = runtime.NumCPU()
	}

	//a WaitGroup to help us wait for all compilers to shut down
	wg := &sync.WaitGroup{}
	wg.Add(numCompilers)

	//spin up the concurrent compilers
	for i := 0; i < numCompilers; i++ {
		go func() {
			defer wg.Done()
			for input := range workPool {
				if r.interruptHandler.WasInterrupted() {
					return
				}

				if willCompile != nil {
					willCompile(input.runner.Suite)
				}

				//We retry because Go sometimes steps on itself when multiple compiles happen in parallel.  This is ugly, but should help resolve flakiness...
				var err error
				retries := 0
				for retries <= 5 {
					if r.interruptHandler.WasInterrupted() {
						return
					}
					if err = input.runner.Compile(); err == nil {
						break
					}
					retries++
				}

				input.result <- compilationOutput{input.runner, err}
			}
		}()
	}

	//read from the compilation output channels *in order* and send them to the caller
	//close the compilationOutputs channel to tell the caller we're done
	go func() {
		defer close(compilationOutputs)
		for _, orderedCompilationOutput := range orderedCompilationOutputs {
			select {
			case compilationOutput := <-orderedCompilationOutput:
				compilationOutputs <- compilationOutput
			case <-r.interruptHandler.C:
				//interrupt detected, wait for the compilers to shut down then bail
				//this ensure we clean up after ourselves as we don't leave any compilation processes running
				wg.Wait()
				return
			}
		}
	}()

	return compilationOutputs
}

func (r *SuiteRunner) RunSuites(runners []*testrunner.TestRunner, numCompilers int, keepGoing bool, willCompile func(suite testsuite.TestSuite)) (testrunner.RunResult, int) {
	runResult := testrunner.PassingRunResult()

	compilationOutputs := r.compileInParallel(runners, numCompilers, willCompile)

	numSuitesThatRan := 0
	suitesThatFailed := []testsuite.TestSuite{}
	for compilationOutput := range compilationOutputs {
		if compilationOutput.err != nil {
			fmt.Print(compilationOutput.err.Error())
		}
		numSuitesThatRan++
		suiteRunResult := testrunner.FailingRunResult()
		if compilationOutput.err == nil {
			suiteRunResult = compilationOutput.runner.Run()
		}
		r.notifier.SendSuiteCompletionNotification(compilationOutput.runner.Suite, suiteRunResult.Passed)
		r.notifier.RunCommand(compilationOutput.runner.Suite, suiteRunResult.Passed)
		runResult = runResult.Merge(suiteRunResult)
		if !suiteRunResult.Passed {
			suitesThatFailed = append(suitesThatFailed, compilationOutput.runner.Suite)
			if !keepGoing {
				break
			}
		}
		if numSuitesThatRan < len(runners) && !config.DefaultReporterConfig.Succinct {
			fmt.Println("")
		}
	}

	if keepGoing && !runResult.Passed {
		r.listFailedSuites(suitesThatFailed)
	}

	return runResult, numSuitesThatRan
}

func (r *SuiteRunner) listFailedSuites(suitesThatFailed []testsuite.TestSuite) {
	fmt.Println("")
	fmt.Println("There were failures detected in the following suites:")

	maxPackageNameLength := 0
	for _, suite := range suitesThatFailed {
		if len(suite.PackageName) > maxPackageNameLength {
			maxPackageNameLength = len(suite.PackageName)
		}
	}

	packageNameFormatter := fmt.Sprintf("%%%ds", maxPackageNameLength)

	for _, suite := range suitesThatFailed {
		if config.DefaultReporterConfig.NoColor {
			fmt.Printf("\t"+packageNameFormatter+" %s\n", suite.PackageName, suite.Path)
		} else {
			fmt.Printf("\t%s"+packageNameFormatter+"%s %s%s%s\n", redColor, suite.PackageName, defaultStyle, lightGrayColor, suite.Path, defaultStyle)
		}
	}
}