diff --git a/options.go b/options.go index 5858428..a1235ee 100644 --- a/options.go +++ b/options.go @@ -13,12 +13,17 @@ type Options struct { // Print step definitions found and exit ShowStepDefinitions bool - // Run scenarios in random order. + // RandomSeed, if not `0`, will be used to run scenarios in a random order. // - // This is especially helpful for detecting situations - // where you have state leaking between scenarios, which - // can cause flickering or fragile tests. - RandomOrder bool + // Randomizing scenario order is especially helpful for detecting + // situations where you have state leaking between scenarios, which can + // cause flickering or fragile tests. + // + // The default value of `0` means "do not randomize". + // + // The magic value of `-1` means "pick a random seed for me", the resulting + // seed will only be between `1-99999` for ease of specification. + RandomSeed int64 // Stops on the first failure StopOnFailure bool diff --git a/run.go b/run.go index 50f93b2..bae334a 100644 --- a/run.go +++ b/run.go @@ -13,7 +13,7 @@ import ( type initializer func(*Suite) type runner struct { - randomOrder bool + randomSeed int64 stopOnFailure bool features []*feature fmt Formatter @@ -33,7 +33,7 @@ func (r *runner) concurrent(rate int) (failed bool) { } suite := &Suite{ fmt: r.fmt, - randomOrder: r.randomOrder, + randomSeed: r.randomSeed, stopOnFailure: r.stopOnFailure, features: []*feature{feat}, } @@ -58,7 +58,7 @@ func (r *runner) concurrent(rate int) (failed bool) { func (r *runner) run() (failed bool) { suite := &Suite{ fmt: r.fmt, - randomOrder: r.randomOrder, + randomSeed: r.randomSeed, stopOnFailure: r.stopOnFailure, features: r.features, } @@ -111,20 +111,26 @@ func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Opt 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, - randomOrder: opt.RandomOrder, + randomSeed: seed, stopOnFailure: opt.StopOnFailure, } - if opt.RandomOrder { - // TODO(mroth): allow for seed to be specified in options, - // and print it at the end of a run for replication purposes - rand.Seed(time.Now().UTC().UnixNano()) - } - var failed bool if opt.Concurrency > 1 { failed = r.concurrent(opt.Concurrency) diff --git a/suite.go b/suite.go index 18cd7f8..e386f75 100644 --- a/suite.go +++ b/suite.go @@ -50,7 +50,7 @@ type Suite struct { fmt Formatter failed bool - randomOrder bool + randomSeed int64 stopOnFailure bool // suite event handlers @@ -336,7 +336,7 @@ func (s *Suite) runFeature(f *feature) { // make a local copy of the feature scenario defenitions, // then shuffle it if we are randomizing scenarios scenarios := make([]interface{}, len(f.ScenarioDefinitions)) - if s.randomOrder { + if s.randomSeed != 0 { perm := rand.Perm(len(f.ScenarioDefinitions)) for i, v := range perm { scenarios[v] = f.ScenarioDefinitions[i]