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)
}
}
}
|