diff --git a/fmt.go b/fmt.go index 8aff4aa..0123969 100644 --- a/fmt.go +++ b/fmt.go @@ -105,8 +105,8 @@ type Formatter interface { Defined(*gherkin.Step, *StepDef) Failed(*gherkin.Step, *StepDef, error) Passed(*gherkin.Step, *StepDef) - Skipped(*gherkin.Step) - Undefined(*gherkin.Step) + Skipped(*gherkin.Step, *StepDef) + Undefined(*gherkin.Step, *StepDef) Pending(*gherkin.Step, *StepDef) Summary() } @@ -210,21 +210,23 @@ func (f *basefmt) Passed(step *gherkin.Step, match *StepDef) { f.passed = append(f.passed, s) } -func (f *basefmt) Skipped(step *gherkin.Step) { +func (f *basefmt) Skipped(step *gherkin.Step, match *StepDef) { s := &stepResult{ owner: f.owner, feature: f.features[len(f.features)-1], step: step, + def: match, typ: skipped, } f.skipped = append(f.skipped, s) } -func (f *basefmt) Undefined(step *gherkin.Step) { +func (f *basefmt) Undefined(step *gherkin.Step, match *StepDef) { s := &stepResult{ owner: f.owner, feature: f.features[len(f.features)-1], step: step, + def: match, typ: undefined, } f.undefined = append(f.undefined, s) @@ -394,38 +396,46 @@ func (f *basefmt) snippets() string { var snips []*undefinedSnippet // build snippets for _, u := range f.undefined { - expr := snippetExprCleanup.ReplaceAllString(u.step.Text, "\\$1") - expr = snippetNumbers.ReplaceAllString(expr, "(\\d+)") - expr = snippetExprQuoted.ReplaceAllString(expr, "$1\"([^\"]*)\"$2") - expr = "^" + strings.TrimSpace(expr) + "$" + steps := []string{u.step.Text} + arg := u.step.Argument + if u.def != nil { + steps = u.def.undefined + arg = nil + } + for _, step := range steps { + expr := snippetExprCleanup.ReplaceAllString(step, "\\$1") + expr = snippetNumbers.ReplaceAllString(expr, "(\\d+)") + expr = snippetExprQuoted.ReplaceAllString(expr, "$1\"([^\"]*)\"$2") + expr = "^" + strings.TrimSpace(expr) + "$" - name := snippetNumbers.ReplaceAllString(u.step.Text, " ") - name = snippetExprQuoted.ReplaceAllString(name, " ") - name = snippetMethodName.ReplaceAllString(name, "") - var words []string - for i, w := range strings.Split(name, " ") { - if i != 0 { - w = strings.Title(w) - } else { - w = string(unicode.ToLower(rune(w[0]))) + w[1:] + name := snippetNumbers.ReplaceAllString(step, " ") + name = snippetExprQuoted.ReplaceAllString(name, " ") + name = snippetMethodName.ReplaceAllString(name, "") + var words []string + for i, w := range strings.Split(name, " ") { + if i != 0 { + w = strings.Title(w) + } else { + w = string(unicode.ToLower(rune(w[0]))) + w[1:] + } + words = append(words, w) + } + name = strings.Join(words, "") + if len(name) == 0 { + index++ + name = fmt.Sprintf("stepDefinition%d", index) } - words = append(words, w) - } - name = strings.Join(words, "") - if len(name) == 0 { - index++ - name = fmt.Sprintf("stepDefinition%d", index) - } - var found bool - for _, snip := range snips { - if snip.Expr == expr { - found = true - break + var found bool + for _, snip := range snips { + if snip.Expr == expr { + found = true + break + } + } + if !found { + snips = append(snips, &undefinedSnippet{Method: name, Expr: expr, argument: arg}) } - } - if !found { - snips = append(snips, &undefinedSnippet{Method: name, Expr: expr, argument: u.step.Argument}) } } diff --git a/fmt_cucumber.go b/fmt_cucumber.go index b0afe03..19897ef 100644 --- a/fmt_cucumber.go +++ b/fmt_cucumber.go @@ -297,16 +297,16 @@ func (f *cukefmt) Passed(step *gherkin.Step, match *StepDef) { f.step(f.passed[len(f.passed)-1]) } -func (f *cukefmt) Skipped(step *gherkin.Step) { - f.basefmt.Skipped(step) +func (f *cukefmt) Skipped(step *gherkin.Step, match *StepDef) { + f.basefmt.Skipped(step, match) f.step(f.skipped[len(f.skipped)-1]) // no duration reported for skipped. f.curStep.Result.Duration = nil } -func (f *cukefmt) Undefined(step *gherkin.Step) { - f.basefmt.Undefined(step) +func (f *cukefmt) Undefined(step *gherkin.Step, match *StepDef) { + f.basefmt.Undefined(step, match) f.stat = undefined f.step(f.undefined[len(f.undefined)-1]) diff --git a/fmt_events.go b/fmt_events.go index 275b021..17e92ff 100644 --- a/fmt_events.go +++ b/fmt_events.go @@ -245,13 +245,13 @@ func (f *events) Passed(step *gherkin.Step, match *StepDef) { f.step(f.passed[len(f.passed)-1]) } -func (f *events) Skipped(step *gherkin.Step) { - f.basefmt.Skipped(step) +func (f *events) Skipped(step *gherkin.Step, match *StepDef) { + f.basefmt.Skipped(step, match) f.step(f.skipped[len(f.skipped)-1]) } -func (f *events) Undefined(step *gherkin.Step) { - f.basefmt.Undefined(step) +func (f *events) Undefined(step *gherkin.Step, match *StepDef) { + f.basefmt.Undefined(step, match) f.stat = undefined f.step(f.undefined[len(f.undefined)-1]) } diff --git a/fmt_junit.go b/fmt_junit.go index de488f6..8762cbe 100644 --- a/fmt_junit.go +++ b/fmt_junit.go @@ -101,7 +101,7 @@ func (j *junitFormatter) Passed(step *gherkin.Step, match *StepDef) { tcase.Status = "passed" } -func (j *junitFormatter) Skipped(step *gherkin.Step) { +func (j *junitFormatter) Skipped(step *gherkin.Step, match *StepDef) { suite := j.current() tcase := suite.current() @@ -112,7 +112,7 @@ func (j *junitFormatter) Skipped(step *gherkin.Step) { }) } -func (j *junitFormatter) Undefined(step *gherkin.Step) { +func (j *junitFormatter) Undefined(step *gherkin.Step, match *StepDef) { suite := j.current() tcase := suite.current() if tcase.Status != "undefined" { diff --git a/fmt_pretty.go b/fmt_pretty.go index f6788c6..7b713df 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -334,13 +334,13 @@ func (f *pretty) Passed(step *gherkin.Step, match *StepDef) { f.printStepKind(f.passed[len(f.passed)-1]) } -func (f *pretty) Skipped(step *gherkin.Step) { - f.basefmt.Skipped(step) +func (f *pretty) Skipped(step *gherkin.Step, match *StepDef) { + f.basefmt.Skipped(step, match) f.printStepKind(f.skipped[len(f.skipped)-1]) } -func (f *pretty) Undefined(step *gherkin.Step) { - f.basefmt.Undefined(step) +func (f *pretty) Undefined(step *gherkin.Step, match *StepDef) { + f.basefmt.Undefined(step, match) f.printStepKind(f.undefined[len(f.undefined)-1]) } diff --git a/fmt_progress.go b/fmt_progress.go index 6e1cc1a..7bb8650 100644 --- a/fmt_progress.go +++ b/fmt_progress.go @@ -91,17 +91,17 @@ func (f *progress) Passed(step *gherkin.Step, match *StepDef) { f.step(f.passed[len(f.passed)-1]) } -func (f *progress) Skipped(step *gherkin.Step) { +func (f *progress) Skipped(step *gherkin.Step, match *StepDef) { f.Lock() defer f.Unlock() - f.basefmt.Skipped(step) + f.basefmt.Skipped(step, match) f.step(f.skipped[len(f.skipped)-1]) } -func (f *progress) Undefined(step *gherkin.Step) { +func (f *progress) Undefined(step *gherkin.Step, match *StepDef) { f.Lock() defer f.Unlock() - f.basefmt.Undefined(step) + f.basefmt.Undefined(step, match) f.step(f.undefined[len(f.undefined)-1]) } diff --git a/fmt_progress_test.go b/fmt_progress_test.go index 114e913..d722fee 100644 --- a/fmt_progress_test.go +++ b/fmt_progress_test.go @@ -92,8 +92,8 @@ var basicGherkinFeature = ` Feature: basic Scenario: passing scenario - When step1 - Then step2 + When one + Then two ` func TestProgressFormatterWhenStepPanics(t *testing.T) { @@ -108,8 +108,8 @@ func TestProgressFormatterWhenStepPanics(t *testing.T) { fmt: progressFunc("progress", w), features: []*feature{&feature{Feature: feat}}, initializer: func(s *Suite) { - s.Step(`^step1$`, func() error { return nil }) - s.Step(`^step2$`, func() error { panic("omg") }) + s.Step(`^one$`, func() error { return nil }) + s.Step(`^two$`, func() error { panic("omg") }) }, } @@ -137,9 +137,9 @@ func TestProgressFormatterWithPassingMultisteps(t *testing.T) { initializer: func(s *Suite) { s.Step(`^sub1$`, func() error { return nil }) s.Step(`^sub-sub$`, func() error { return nil }) - s.Step(`^sub2$`, func() Steps { return Steps{"sub-sub", "sub1", "step1"} }) - s.Step(`^step1$`, func() error { return nil }) - s.Step(`^step2$`, func() Steps { return Steps{"sub1", "sub2"} }) + s.Step(`^sub2$`, func() Steps { return Steps{"sub-sub", "sub1", "one"} }) + s.Step(`^one$`, func() error { return nil }) + s.Step(`^two$`, func() Steps { return Steps{"sub1", "sub2"} }) }, } @@ -162,9 +162,9 @@ func TestProgressFormatterWithFailingMultisteps(t *testing.T) { initializer: func(s *Suite) { s.Step(`^sub1$`, func() error { return nil }) s.Step(`^sub-sub$`, func() error { return fmt.Errorf("errored") }) - s.Step(`^sub2$`, func() Steps { return Steps{"sub-sub", "sub1", "step1"} }) - s.Step(`^step1$`, func() error { return nil }) - s.Step(`^step2$`, func() Steps { return Steps{"sub1", "sub2"} }) + s.Step(`^sub2$`, func() Steps { return Steps{"sub-sub", "sub1", "one"} }) + s.Step(`^one$`, func() error { return nil }) + s.Step(`^two$`, func() Steps { return Steps{"sub1", "sub2"} }) }, } @@ -187,9 +187,9 @@ func TestProgressFormatterWithPanicInMultistep(t *testing.T) { initializer: func(s *Suite) { s.Step(`^sub1$`, func() error { return nil }) s.Step(`^sub-sub$`, func() error { return nil }) - s.Step(`^sub2$`, func() []string { return []string{"sub-sub", "sub1", "step1"} }) - s.Step(`^step1$`, func() error { return nil }) - s.Step(`^step2$`, func() []string { return []string{"sub1", "sub2"} }) + s.Step(`^sub2$`, func() []string { return []string{"sub-sub", "sub1", "one"} }) + s.Step(`^one$`, func() error { return nil }) + s.Step(`^two$`, func() []string { return []string{"sub1", "sub2"} }) }, } @@ -197,3 +197,67 @@ func TestProgressFormatterWithPanicInMultistep(t *testing.T) { t.Fatal("the suite should have failed") } } + +func TestProgressFormatterMultistepTemplates(t *testing.T) { + feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var buf bytes.Buffer + w := colors.Uncolored(&buf) + r := runner{ + fmt: progressFunc("progress", w), + features: []*feature{&feature{Feature: feat}}, + initializer: func(s *Suite) { + s.Step(`^sub-sub$`, func() error { return nil }) + s.Step(`^substep$`, func() Steps { return Steps{"sub-sub", `unavailable "John" cost 5`, "one", "three"} }) + s.Step(`^one$`, func() error { return nil }) + s.Step(`^(t)wo$`, func(s string) Steps { return Steps{"undef", "substep"} }) + }, + } + + if r.run() { + t.Fatal("the suite should have passed") + } + + expected := ` +.U 2 + + +1 scenarios (1 undefined) +2 steps (1 passed, 1 undefined) +%s + +Randomized with seed: %s + +You can implement step definitions for undefined steps with these snippets: + +func undef() error { + return godog.ErrPending +} + +func unavailableCost(arg1 string, arg2 int) error { + return godog.ErrPending +} + +func three() error { + return godog.ErrPending +} + +func FeatureContext(s *godog.Suite) { + s.Step(` + "`^undef$`" + `, undef) + s.Step(` + "`^unavailable \"([^\"]*)\" cost (\\d+)$`" + `, unavailableCost) + s.Step(` + "`^three$`" + `, three) +} +` + + var zeroDuration time.Duration + expected = fmt.Sprintf(expected, zeroDuration.String(), os.Getenv("GODOG_SEED")) + expected = trimAllLines(expected) + + actual := trimAllLines(buf.String()) + if actual != expected { + t.Fatalf("expected output does not match: %s", actual) + } +} diff --git a/run_test.go b/run_test.go index 06d898c..6910d2c 100644 --- a/run_test.go +++ b/run_test.go @@ -2,10 +2,13 @@ package godog import ( "bytes" + "fmt" + "io/ioutil" "strings" "testing" "github.com/DATA-DOG/godog/colors" + "github.com/DATA-DOG/godog/gherkin" ) func okStep() error { @@ -50,3 +53,43 @@ func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) { t.Fatalf("expected output does not match to: %s", out) } } + +func TestShouldNotFailWhenHasPendingSteps(t *testing.T) { + feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + r := runner{ + fmt: progressFunc("progress", ioutil.Discard), + features: []*feature{&feature{Feature: feat}}, + initializer: func(s *Suite) { + s.Step(`^one$`, func() error { return nil }) + s.Step(`^two$`, func() error { return ErrPending }) + }, + } + + if r.run() { + t.Fatal("the suite should have passed") + } +} + +func TestShouldFailOnError(t *testing.T) { + feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + r := runner{ + fmt: progressFunc("progress", ioutil.Discard), + features: []*feature{&feature{Feature: feat}}, + initializer: func(s *Suite) { + s.Step(`^one$`, func() error { return nil }) + s.Step(`^two$`, func() error { return fmt.Errorf("error") }) + }, + } + + if !r.run() { + t.Fatal("the suite should have failed") + } +} diff --git a/stepdef.go b/stepdef.go index 6233a17..1241b55 100644 --- a/stepdef.go +++ b/stepdef.go @@ -43,7 +43,10 @@ type StepDef struct { hv reflect.Value Expr *regexp.Regexp Handler interface{} - nested bool + + // multistep related + nested bool + undefined []string } func (sd *StepDef) definitionID() string { diff --git a/suite.go b/suite.go index 0182650..74ed3f6 100644 --- a/suite.go +++ b/suite.go @@ -113,10 +113,10 @@ func (s *Suite) Step(expr interface{}, stepFunc interface{}) { panic(fmt.Sprintf("expected handler to return an error, but got: %s", typ.Kind())) } case reflect.Slice: - def.nested = true if typ.Elem().Kind() != reflect.String { panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind())) } + def.nested = true default: panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind())) } @@ -243,14 +243,23 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) { } }() - // @TODO custom undefined err here to pass step text for snippet - if s.maybeUndefined(match) { - s.fmt.Undefined(step) + if undef := s.maybeUndefined(step.Text); len(undef) > 0 { + if match != nil { + match = &StepDef{ + args: match.args, + hv: match.hv, + Expr: match.Expr, + Handler: match.Handler, + nested: match.nested, + undefined: undef, + } + } + s.fmt.Undefined(step, match) return ErrUndefined } if prevStepErr != nil { - s.fmt.Skipped(step) + s.fmt.Skipped(step, match) return nil } @@ -263,21 +272,21 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) { return } -func (s *Suite) maybeUndefined(step *StepDef) bool { +func (s *Suite) maybeUndefined(text string) (undefined []string) { + step := s.matchStepText(text) if nil == step { - return true + undefined = append(undefined, text) + return } if !step.nested { - return false + return } - for _, text := range step.run().(Steps) { - if s.maybeUndefined(s.matchStepText(text)) { - return true - } + for _, next := range step.run().(Steps) { + undefined = append(undefined, s.maybeUndefined(next)...) } - return false + return } func (s *Suite) maybeSubSteps(result interface{}) error { @@ -345,7 +354,7 @@ func (s *Suite) runSteps(steps []*gherkin.Step, prevErr error) (err error) { func (s *Suite) skipSteps(steps []*gherkin.Step) { for _, step := range steps { - s.fmt.Skipped(step) + s.fmt.Skipped(step, s.matchStep(step)) } }