godog/run.go
Matthew Rothenberg cf6160b3f2 fix deterministic scenario ordering with feature concurrency
while global rand is concurrent safe, since each suite is handling
picking scenario order on it’s own, if running features under
concurrency we cannot guarantee the order Features will execute under,
therefore we have each suite initialize its own randomness Source with
the initial seed, such that their random order selection within a
Feature should always be deterministic when given a specific seed.
2017-04-25 12:33:20 -04:00

179 строки
3,8 КиБ
Go

package godog
import (
"fmt"
"io"
"math/rand"
"os"
"time"
"github.com/DATA-DOG/godog/colors"
)
type initializer func(*Suite)
type runner struct {
randomSeed int64
stopOnFailure bool
features []*feature
fmt Formatter
initializer initializer
}
func (r *runner) concurrent(rate int) (failed bool) {
queue := make(chan int, rate)
for i, ft := range r.features {
queue <- i // reserve space in queue
go func(fail *bool, feat *feature) {
defer func() {
<-queue // free a space in queue
}()
if r.stopOnFailure && *fail {
return
}
suite := &Suite{
fmt: r.fmt,
randomSeed: r.randomSeed,
stopOnFailure: r.stopOnFailure,
features: []*feature{feat},
}
r.initializer(suite)
suite.run()
if suite.failed {
*fail = true
}
}(&failed, ft)
}
// wait until last are processed
for i := 0; i < rate; i++ {
queue <- i
}
close(queue)
// print summary
r.fmt.Summary()
return
}
func (r *runner) run() (failed bool) {
suite := &Suite{
fmt: r.fmt,
randomSeed: r.randomSeed,
stopOnFailure: r.stopOnFailure,
features: r.features,
}
r.initializer(suite)
suite.run()
r.fmt.Summary()
return suite.failed
}
// RunWithOptions is same as Run function, except
// it uses Options provided in order to run the
// test suite without parsing flags
//
// This method is useful in case if you run
// godog in for example TestMain function together
// with go tests
func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Options) int {
var output io.Writer = os.Stdout
if nil != opt.Output {
output = opt.Output
}
if opt.NoColors {
output = colors.Uncolored(output)
} else {
output = colors.Colored(output)
}
if opt.ShowStepDefinitions {
s := &Suite{}
contextInitializer(s)
s.printStepDefinitions()
return 0
}
if len(opt.Paths) == 0 {
inf, err := os.Stat("features")
if err == nil && inf.IsDir() {
opt.Paths = []string{"features"}
}
}
if opt.Concurrency > 1 && !supportsConcurrency(opt.Format) {
fatal(fmt.Errorf("format \"%s\" does not support concurrent execution", opt.Format))
}
formatter, err := findFmt(opt.Format)
fatal(err)
features, err := parseFeatures(opt.Tags, opt.Paths)
fatal(err)
seed := opt.RandomSeed
if seed == -1 {
// if RandomSeed opt is -1, means pick a memorable rand seed as default
// the docStrings for Option specify this should be 1-99999 (same as ruby Cucumber)
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
seed = r.Int63n(99998) + 1
}
r := runner{
fmt: formatter(suite, output),
initializer: contextInitializer,
features: features,
randomSeed: seed,
stopOnFailure: opt.StopOnFailure,
}
var failed bool
if opt.Concurrency > 1 {
failed = r.concurrent(opt.Concurrency)
} else {
failed = r.run()
}
if failed && opt.Format != "events" {
return 1
}
return 0
}
// Run creates and runs the feature suite.
// Reads all configuration options from flags.
// uses contextInitializer to register contexts
//
// the concurrency option allows runner to
// initialize a number of suites to be run
// separately. Only progress formatter
// is supported when concurrency level is
// higher than 1
//
// contextInitializer must be able to register
// the step definitions and event handlers.
func Run(suite string, contextInitializer func(suite *Suite)) int {
var opt Options
opt.Output = colors.Colored(os.Stdout)
flagSet := FlagSet(&opt)
err := flagSet.Parse(os.Args[1:])
fatal(err)
opt.Paths = flagSet.Args()
return RunWithOptions(suite, contextInitializer, opt)
}
func supportsConcurrency(format string) bool {
switch format {
case "events":
return false
case "junit":
return false
case "pretty":
return false
case "cucumber":
return false
}
return true // all custom formatters are treated as supporting concurrency
}