
This allows for us to propagate the seed value around, and not have to have two different values in Options when we want to allow specification of seed. It does introduce some “magic values”, but the document them in the Options struct docstrings.
183 строки
3,9 КиБ
Go
183 строки
3,9 КиБ
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
|
|
}
|
|
if seed != 0 {
|
|
// init global rand module (concurrent safe) with our seed
|
|
rand.Seed(seed)
|
|
}
|
|
|
|
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
|
|
}
|