diff --git a/CHANGELOG.md b/CHANGELOG.md index a48c3c83..dfdd8d9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt ### Changed - README example is updated with `context.Context` and `go test` usage. ([477](https://github.com/cucumber/godog/pull/477) - [vearutop](https://github.com/vearutop)) +- Removed deprecation of `godog.BindFlags` ([498](https://github.com/cucumber/godog/pull/498) - [vearutop](https://github.com/vearutop)) ### Fixed - Fixed a bug which would ignore the context returned from a substep.([488](https://github.com/cucumber/godog/pull/488) - [wichert](https://github.com/wichert)) diff --git a/_examples/godogs/features/godogs.feature b/_examples/godogs/features/godogs.feature index 9ea25ccd..101a6a03 100644 --- a/_examples/godogs/features/godogs.feature +++ b/_examples/godogs/features/godogs.feature @@ -1,4 +1,3 @@ -# file: $GOPATH/godogs/features/godogs.feature Feature: eat godogs In order to be happy As a hungry gopher @@ -8,3 +7,8 @@ Feature: eat godogs Given there are 12 godogs When I eat 5 Then there should be 7 remaining + + Scenario: Eat 12 out of 12 + Given there are 12 godogs + When I eat 12 + Then there should be none remaining diff --git a/_examples/godogs/features/nodogs.feature b/_examples/godogs/features/nodogs.feature new file mode 100644 index 00000000..699a3a87 --- /dev/null +++ b/_examples/godogs/features/nodogs.feature @@ -0,0 +1,14 @@ +Feature: do not eat godogs + In order to be fit + As a well-fed gopher + I need to be able to avoid godogs + + Scenario: Eat 0 out of 12 + Given there are 12 godogs + When I eat 0 + Then there should be 12 remaining + + Scenario: Eat 0 out of 0 + Given there are 0 godogs + When I eat 0 + Then there should be none remaining diff --git a/_examples/godogs/godogs.go b/_examples/godogs/godogs.go index e71f308c..f8604e26 100644 --- a/_examples/godogs/godogs.go +++ b/_examples/godogs/godogs.go @@ -1,6 +1,4 @@ -package main +package godogs -// Godogs available to eat +// Godogs available to eat. var Godogs int - -func main() { /* usual main func */ } diff --git a/_examples/godogs/godogs_test.go b/_examples/godogs/godogs_test.go index f458c5ad..ec0965f3 100644 --- a/_examples/godogs/godogs_test.go +++ b/_examples/godogs/godogs_test.go @@ -1,34 +1,51 @@ -package main +package godogs + +// This example shows how to set up test suite runner with Go subtests and godog command line parameters. +// Sample commands: +// * run all scenarios from default directory (features): go test -test.run "^TestFeatures/" +// * run all scenarios and list subtest names: go test -test.v -test.run "^TestFeatures/" +// * run all scenarios from one feature file: go test -test.v -godog.paths features/nodogs.feature -test.run "^TestFeatures/" +// * run all scenarios from multiple feature files: go test -test.v -godog.paths features/nodogs.feature,features/godogs.feature -test.run "^TestFeatures/" +// * run single scenario as a subtest: go test -test.v -test.run "^TestFeatures/Eat_5_out_of_12$" +// * show usage help: go test -godog.help +// * show usage help if there were other test files in directory: go test -godog.help godogs_test.go +// * run scenarios with multiple formatters: go test -test.v -godog.format cucumber:cuc.json,pretty -test.run "^TestFeatures/" import ( "context" + "flag" "fmt" "os" "testing" "github.com/cucumber/godog" "github.com/cucumber/godog/colors" - "github.com/spf13/pflag" ) var opts = godog.Options{Output: colors.Colored(os.Stdout)} func init() { - godog.BindCommandLineFlags("godog.", &opts) + godog.BindFlags("godog.", flag.CommandLine, &opts) } -func TestMain(m *testing.M) { - pflag.Parse() - opts.Paths = pflag.Args() +func TestFeatures(t *testing.T) { + o := opts + o.TestingT = t status := godog.TestSuite{ Name: "godogs", + Options: &o, TestSuiteInitializer: InitializeTestSuite, ScenarioInitializer: InitializeScenario, - Options: &opts, }.Run() - os.Exit(status) + if status == 2 { + t.SkipNow() + } + + if status != 0 { + t.Fatalf("zero status code expected, %d received", status) + } } func thereAreGodogs(available int) error { @@ -51,6 +68,10 @@ func thereShouldBeRemaining(remaining int) error { return nil } +func thereShouldBeNoneRemaining() error { + return thereShouldBeRemaining(0) +} + func InitializeTestSuite(ctx *godog.TestSuiteContext) { ctx.BeforeSuite(func() { Godogs = 0 }) } @@ -64,4 +85,5 @@ func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^there are (\d+) godogs$`, thereAreGodogs) ctx.Step(`^I eat (\d+)$`, iEat) ctx.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining) + ctx.Step(`^there should be none remaining$`, thereShouldBeNoneRemaining) } diff --git a/flags_deprecated.go b/flags.go similarity index 90% rename from flags_deprecated.go rename to flags.go index b7b0f2b6..14782e16 100644 --- a/flags_deprecated.go +++ b/flags.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "io" + "sort" "strconv" "strings" @@ -18,7 +19,8 @@ var descFeaturesArgument = "Optional feature(s) to run. Can be:\n" + s(4) + "- dir " + colors.Yellow("(features/)") + "\n" + s(4) + "- feature " + colors.Yellow("(*.feature)") + "\n" + s(4) + "- scenario at specific line " + colors.Yellow("(*.feature:10)") + "\n" + - "If no feature paths are listed, suite tries " + colors.Yellow("features") + " path by default.\n" + "If no feature paths are listed, suite tries " + colors.Yellow("features") + " path by default.\n" + + "Multiple comma-separated values can be provided.\n" var descConcurrencyOption = "Run the test suite with concurrency level:\n" + s(4) + "- " + colors.Yellow(`= 1`) + ": supports all types of formats.\n" + @@ -48,15 +50,30 @@ func FlagSet(opt *Options) *flag.FlagSet { // BindFlags binds godog flags to given flag set prefixed // by given prefix, without overriding usage -// -// Deprecated: Use BindCommandLineFlags(prefix, &opts) -// instead of BindFlags(prefix, flag.CommandLine, &opts) func BindFlags(prefix string, set *flag.FlagSet, opt *Options) { + set.Usage = usage(set, set.Output()) + descFormatOption := "How to format tests output. Built-in formats:\n" - // @TODO: sort by name + + type fm struct { + name string + desc string + } + var fms []fm for name, desc := range AvailableFormatters() { - descFormatOption += s(4) + "- " + colors.Yellow(name) + ": " + desc + "\n" + fms = append(fms, fm{ + name: name, + desc: desc, + }) } + sort.Slice(fms, func(i, j int) bool { + return fms[i].name < fms[j].name + }) + + for _, fm := range fms { + descFormatOption += s(4) + "- " + colors.Yellow(fm.name) + ": " + fm.desc + "\n" + } + descFormatOption = strings.TrimSpace(descFormatOption) // override flag defaults if any corresponding properties were supplied on the incoming `opt` @@ -107,6 +124,14 @@ func BindFlags(prefix string, set *flag.FlagSet, opt *Options) { set.BoolVar(&opt.Strict, prefix+"strict", defStrict, "Fail suite when there are pending or undefined steps.") set.BoolVar(&opt.NoColors, prefix+"no-colors", defNoColors, "Disable ansi colors.") set.Var(&randomSeed{&opt.Randomize}, prefix+"random", descRandomOption) + set.BoolVar(&opt.ShowHelp, "godog.help", false, "Show usage help.") + set.Func(prefix+"paths", descFeaturesArgument, func(paths string) error { + if paths != "" { + opt.Paths = strings.Split(paths, ",") + } + + return nil + }) } type flagged struct { @@ -183,15 +208,7 @@ func usage(set *flag.FlagSet, w io.Writer) func() { // --- GENERAL --- fmt.Fprintln(w, colors.Yellow("Usage:")) - fmt.Fprintf(w, s(2)+"godog [options] []\n\n") - // description - fmt.Fprintln(w, "Builds a test package and runs given feature files.") - fmt.Fprintf(w, "Command should be run from the directory of tested package and contain buildable go source.\n\n") - - // --- ARGUMENTS --- - fmt.Fprintln(w, colors.Yellow("Arguments:")) - // --> features - fmt.Fprintln(w, opt("features", descFeaturesArgument)) + fmt.Fprintf(w, s(2)+"go test [options]\n\n") // --- OPTIONS --- fmt.Fprintln(w, colors.Yellow("Options:")) diff --git a/flags_deprecated_test.go b/flags_test.go similarity index 98% rename from flags_deprecated_test.go rename to flags_test.go index 5390d677..d00efc11 100644 --- a/flags_deprecated_test.go +++ b/flags_test.go @@ -121,7 +121,9 @@ func TestBindFlagsShouldRespectOptDefaults(t *testing.T) { Randomize: int64(7), } - BindFlags("optDefaults.", flag.CommandLine, &opts) + flagSet := flag.FlagSet{} + + BindFlags("optDefaults.", &flagSet, &opts) if opts.Format != "progress" { t.Fatalf("expected Format: progress, but it was: %s", opts.Format) diff --git a/internal/flags/options.go b/internal/flags/options.go index a0eb1260..f908467e 100644 --- a/internal/flags/options.go +++ b/internal/flags/options.go @@ -69,6 +69,9 @@ type Options struct { // where the contents of each feature is stored as a byte slice // in a map entry FeatureContents []Feature + + // ShowHelp enables suite to show CLI flags usage help and exit. + ShowHelp bool } type Feature struct { diff --git a/run.go b/run.go index f837b921..9041bdcd 100644 --- a/run.go +++ b/run.go @@ -2,6 +2,7 @@ package godog import ( "context" + "flag" "fmt" "go/build" "io" @@ -102,37 +103,44 @@ func (r *runner) concurrent(rate int) (failed bool) { r.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.Content) } - runPickle := func(fail *bool, pickle *messages.Pickle) { - defer func() { - <-queue // free a space in queue - }() - - if r.stopOnFailure && *fail { - return - } - - // Copy base suite. - suite := *testSuiteContext.suite - - if r.scenarioInitializer != nil { - sc := ScenarioContext{suite: &suite} - r.scenarioInitializer(&sc) - } - - err := suite.runPickle(pickle) - if suite.shouldFail(err) { - copyLock.Lock() - *fail = true - copyLock.Unlock() - } - } - - if rate == 1 { - // Running within the same goroutine for concurrency 1 - // to preserve original stacks and simplify debugging. - runPickle(&failed, &pickle) - } else { - go runPickle(&failed, &pickle) + // scenario + if r.testingT != nil { + // Running scenario as a subtest. + r.testingT.Run(pickle.Name, func(t *testing.T) { + + runPickle := func(fail *bool, pickle *messages.Pickle) { + defer func() { + <-queue // free a space in queue + }() + + if r.stopOnFailure && *fail { + return + } + + // Copy base suite. + suite := *testSuiteContext.suite + + if r.scenarioInitializer != nil { + sc := ScenarioContext{suite: &suite, TestingT: t} + r.scenarioInitializer(&sc) + } + + err := suite.runPickle(pickle) + if suite.shouldFail(err) { + copyLock.Lock() + *fail = true + copyLock.Unlock() + } + } + + if rate == 1 { + // Running within the same goroutine for concurrency 1 + // to preserve original stacks and simplify debugging. + runPickle(&failed, &pickle) + } else { + go runPickle(&failed, &pickle) + } + }) } } } @@ -309,10 +317,11 @@ type TestSuite struct { // all configuration options from flags. // // The exit codes may vary from: -// 0 - success -// 1 - failed -// 2 - command line usage error -// 128 - or higher, os signal related error exit codes +// +// 0 - success +// 1 - failed +// 2 - command line usage error +// 128 - or higher, os signal related error exit codes // // If there are flag related errors they will be directed to os.Stderr func (ts TestSuite) Run() int { @@ -323,6 +332,12 @@ func (ts TestSuite) Run() int { return exitOptionError } } + if ts.Options.ShowHelp { + flag.CommandLine.Usage() + + return 0 + } + r := runner{testSuiteInitializer: ts.TestSuiteInitializer, scenarioInitializer: ts.ScenarioInitializer} return runWithOptions(ts.Name, r, *ts.Options) } diff --git a/suite.go b/suite.go index 05860526..89ca2d30 100644 --- a/suite.go +++ b/suite.go @@ -442,18 +442,7 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) { s.fmt.Pickle(pickle) - // scenario - if s.testingT != nil { - // Running scenario as a subtest. - s.testingT.Run(pickle.Name, func(t *testing.T) { - ctx, err = s.runSteps(ctx, pickle, pickle.Steps) - if err != nil { - t.Error(err) - } - }) - } else { - ctx, err = s.runSteps(ctx, pickle, pickle.Steps) - } + _, err = s.runSteps(ctx, pickle, pickle.Steps) // After scenario handlers are called in context of last evaluated step // so that error from handler can be added to step. diff --git a/test_context.go b/test_context.go index fbbe981f..65b6ef67 100644 --- a/test_context.go +++ b/test_context.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "regexp" + "testing" "github.com/cucumber/godog/formatters" "github.com/cucumber/godog/internal/builder" @@ -26,12 +27,12 @@ type Step = messages.PickleStep // instead of returning an error in step func // it is possible to return combined steps: // -// func multistep(name string) godog.Steps { -// return godog.Steps{ -// fmt.Sprintf(`an user named "%s"`, name), -// fmt.Sprintf(`user "%s" is authenticated`, name), -// } -// } +// func multistep(name string) godog.Steps { +// return godog.Steps{ +// fmt.Sprintf(`an user named "%s"`, name), +// fmt.Sprintf(`user "%s" is authenticated`, name), +// } +// } // // These steps will be matched and executed in // sequential order. The first one which fails @@ -104,7 +105,8 @@ func (ctx *TestSuiteContext) ScenarioContext() *ScenarioContext { // executions are catching panic error since it may // be a context specific error. type ScenarioContext struct { - suite *suite + suite *suite + TestingT *testing.T } // StepContext allows registering step hooks.