diff --git a/arguments.go b/arguments.go index 3f5d70d..8484067 100644 --- a/arguments.go +++ b/arguments.go @@ -9,9 +9,9 @@ import ( // the regexp submatch to handle the step type Arg string -// Float converts an argument to float64 +// Float64 converts an argument to float64 // or panics if unable to convert it -func (a Arg) Float() float64 { +func (a Arg) Float64() float64 { v, err := strconv.ParseFloat(string(a), 64) if err == nil { return v @@ -19,17 +19,72 @@ func (a Arg) Float() float64 { panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, a, err)) } -// Int converts an argument to int64 +// Float32 converts an argument to float32 // or panics if unable to convert it -func (a Arg) Int() int64 { +func (a Arg) Float32() float32 { + v, err := strconv.ParseFloat(string(a), 32) + if err == nil { + return float32(v) + } + panic(fmt.Sprintf(`cannot convert "%s" to float32: %s`, a, err)) +} + +// Int converts an argument to int +// or panics if unable to convert it +func (a Arg) Int() int { v, err := strconv.ParseInt(string(a), 10, 0) + if err == nil { + return int(v) + } + panic(fmt.Sprintf(`cannot convert "%s" to int: %s`, a, err)) +} + +// Int64 converts an argument to int64 +// or panics if unable to convert it +func (a Arg) Int64() int64 { + v, err := strconv.ParseInt(string(a), 10, 64) if err == nil { return v } panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, a, err)) } +// Int32 converts an argument to int32 +// or panics if unable to convert it +func (a Arg) Int32() int32 { + v, err := strconv.ParseInt(string(a), 10, 32) + if err == nil { + return int32(v) + } + panic(fmt.Sprintf(`cannot convert "%s" to int32: %s`, a, err)) +} + +// Int16 converts an argument to int16 +// or panics if unable to convert it +func (a Arg) Int16() int16 { + v, err := strconv.ParseInt(string(a), 10, 16) + if err == nil { + return int16(v) + } + panic(fmt.Sprintf(`cannot convert "%s" to int16: %s`, a, err)) +} + +// Int8 converts an argument to int8 +// or panics if unable to convert it +func (a Arg) Int8() int8 { + v, err := strconv.ParseInt(string(a), 10, 8) + if err == nil { + return int8(v) + } + panic(fmt.Sprintf(`cannot convert "%s" to int8: %s`, a, err)) +} + // String converts an argument to string func (a Arg) String() string { return string(a) } + +// Bytes converts an argument string to bytes +func (a Arg) Bytes() []byte { + return []byte(a) +} diff --git a/features/hooks.feature b/features/hooks.feature new file mode 100644 index 0000000..2eb8b35 --- /dev/null +++ b/features/hooks.feature @@ -0,0 +1 @@ +Feature: suite hooks diff --git a/features/load_features.feature b/features/load_features.feature new file mode 100644 index 0000000..91cfe07 --- /dev/null +++ b/features/load_features.feature @@ -0,0 +1,25 @@ +Feature: load features + In order to run features + As a test suite + I need to be able to load features + + Scenario: load features within path + Given a feature path "features" + When I parse features + Then I should have 2 feature files + + Scenario: load a specific feature file + Given a feature path "features/load_features.feature" + When I parse features + Then I should have 1 feature file + + Scenario: load a feature file with a specified scenario + Given a feature path "features/load_features.feature:6" + When I parse features + Then I should have 1 scenario registered + + Scenario: load a number of feature files + Given a feature path "features/load_features.feature" + And a feature path "features/hooks.feature" + When I parse features + Then I should have 2 feature files diff --git a/features/suite.feature b/features/suite.feature deleted file mode 100644 index 71188b0..0000000 --- a/features/suite.feature +++ /dev/null @@ -1,9 +0,0 @@ -Feature: godog bdd suite - In order to test application behavior - As a suite - I need to be able to register and run features - - Scenario: parses all features in path - Given a feature path "features" - When I parse features - Then I should have 5 feature file diff --git a/formatter.go b/formatter.go index 025c66a..c17d4a9 100644 --- a/formatter.go +++ b/formatter.go @@ -103,11 +103,11 @@ func (f *pretty) Node(node interface{}) { f.doneBackground = false f.background = nil f.features = append(f.features, t) - fmt.Println(bcl("Feature: ", white) + t.Title + "\n") + fmt.Println(bcl("Feature: ", white) + t.Title) fmt.Println(t.Description) case *gherkin.Background: f.background = t - fmt.Println(bcl("Background:", white) + "\n") + fmt.Println("\n" + bcl("Background:", white)) case *gherkin.Scenario: f.commentPos = len(t.Token.Text) for _, step := range t.Steps { @@ -117,7 +117,7 @@ func (f *pretty) Node(node interface{}) { } text := strings.Repeat(" ", t.Token.Indent) + bcl("Scenario: ", white) + t.Title text += strings.Repeat(" ", f.commentPos-len(t.Token.Text)+1) + f.line(t.Token) - fmt.Println(text + "\n") + fmt.Println("\n" + text) } } diff --git a/gherkin/lexer.go b/gherkin/lexer.go index 5457f4f..8ed1be6 100644 --- a/gherkin/lexer.go +++ b/gherkin/lexer.go @@ -46,7 +46,7 @@ func (l *lexer) read() *Token { if len(line) == 0 { return &Token{ Type: NEW_LINE, - Line: l.lines - 1, + Line: l.lines, } } // comment @@ -55,7 +55,7 @@ func (l *lexer) read() *Token { return &Token{ Type: COMMENT, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Value: comment, Text: line, Comment: comment, @@ -66,7 +66,7 @@ func (l *lexer) read() *Token { return &Token{ Type: PYSTRING, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Text: line, } } @@ -74,7 +74,7 @@ func (l *lexer) read() *Token { if m := matchers["step"].FindStringSubmatch(line); len(m) > 0 { tok := &Token{ Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Value: strings.TrimSpace(m[3]), Text: line, Comment: strings.Trim(m[4], " #"), @@ -98,7 +98,7 @@ func (l *lexer) read() *Token { return &Token{ Type: SCENARIO, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Value: strings.TrimSpace(m[2]), Text: line, Comment: strings.Trim(m[3], " #"), @@ -109,7 +109,7 @@ func (l *lexer) read() *Token { return &Token{ Type: BACKGROUND, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Text: line, Comment: strings.Trim(m[2], " #"), } @@ -119,7 +119,7 @@ func (l *lexer) read() *Token { return &Token{ Type: FEATURE, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Value: strings.TrimSpace(m[2]), Text: line, Comment: strings.Trim(m[3], " #"), @@ -130,7 +130,7 @@ func (l *lexer) read() *Token { return &Token{ Type: TAGS, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Value: strings.TrimSpace(m[2]), Text: line, Comment: strings.Trim(m[3], " #"), @@ -141,7 +141,7 @@ func (l *lexer) read() *Token { return &Token{ Type: TABLE_ROW, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Value: strings.TrimSpace(m[2]), Text: line, Comment: strings.Trim(m[3], " #"), @@ -152,7 +152,7 @@ func (l *lexer) read() *Token { return &Token{ Type: SCENARIO_OUTLINE, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Value: strings.TrimSpace(m[2]), Text: line, Comment: strings.Trim(m[3], " #"), @@ -163,7 +163,7 @@ func (l *lexer) read() *Token { return &Token{ Type: EXAMPLES, Indent: len(m[1]), - Line: l.lines - 1, + Line: l.lines, Text: line, Comment: strings.Trim(m[2], " #"), } @@ -172,7 +172,7 @@ func (l *lexer) read() *Token { text := strings.TrimLeftFunc(line, unicode.IsSpace) return &Token{ Type: TEXT, - Line: l.lines - 1, + Line: l.lines, Value: text, Indent: len(line) - len(text), Text: line, diff --git a/suite.go b/suite.go index c00d8d5..8c52e1d 100644 --- a/suite.go +++ b/suite.go @@ -8,6 +8,16 @@ import ( "github.com/DATA-DOG/godog/gherkin" ) +type BeforeScenarioHandler interface { + BeforeScenario(scenario *gherkin.Scenario) +} + +type BeforeScenarioHandlerFunc func(scenario *gherkin.Scenario) + +func (f BeforeScenarioHandlerFunc) BeforeScenario(scenario *gherkin.Scenario) { + f(scenario) +} + // Objects implementing the StepHandler interface can be // registered as step definitions in godog // @@ -44,13 +54,15 @@ type stepMatchHandler struct { // Suite is an interface which allows various contexts // to register step definitions and event handlers type Suite interface { - Step(exp *regexp.Regexp, h StepHandler) + Step(expr *regexp.Regexp, h StepHandler) + BeforeScenario(h BeforeScenarioHandler) } type suite struct { - steps []*stepMatchHandler - features []*gherkin.Feature - fmt Formatter + beforeScenarioHandlers []BeforeScenarioHandler + stepHandlers []*stepMatchHandler + features []*gherkin.Feature + fmt Formatter } // New initializes a suite which supports the Suite @@ -71,12 +83,16 @@ func New() *suite { // If none of the StepHandlers are matched, then a pending // step error will be raised. func (s *suite) Step(expr *regexp.Regexp, h StepHandler) { - s.steps = append(s.steps, &stepMatchHandler{ + s.stepHandlers = append(s.stepHandlers, &stepMatchHandler{ handler: h, expr: expr, }) } +func (s *suite) BeforeScenario(h BeforeScenarioHandler) { + s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, h) +} + // Run - runs a godog feature suite func (s *suite) Run() { var err error @@ -98,7 +114,7 @@ func (s *suite) Run() { func (s *suite) runStep(step *gherkin.Step) (err error) { var match *stepMatchHandler var args []Arg - for _, h := range s.steps { + for _, h := range s.stepHandlers { if m := h.expr.FindStringSubmatch(step.Text); len(m) > 0 { match = h for _, a := range m[1:] { @@ -151,6 +167,10 @@ func (s *suite) runFeature(f *gherkin.Feature) { s.fmt.Node(f) var failed bool for _, scenario := range f.Scenarios { + // run before scenario handlers + for _, h := range s.beforeScenarioHandlers { + h.BeforeScenario(scenario) + } // background if f.Background != nil && !failed { s.fmt.Node(f.Background) diff --git a/suite_test.go b/suite_test.go index 69df56d..a5aedce 100644 --- a/suite_test.go +++ b/suite_test.go @@ -3,14 +3,21 @@ package godog import ( "fmt" "regexp" + + "github.com/DATA-DOG/godog/gherkin" ) type suiteFeature struct { suite } +func (s *suiteFeature) BeforeScenario(scenario *gherkin.Scenario) { + // reset feature paths + cfg.paths = []string{} +} + func (s *suiteFeature) featurePath(args ...Arg) error { - cfg.paths = []string{args[0].String()} + cfg.paths = append(cfg.paths, args[0].String()) return nil } @@ -20,17 +27,30 @@ func (s *suiteFeature) parseFeatures(args ...Arg) (err error) { } func (s *suiteFeature) numParsed(args ...Arg) (err error) { - if len(s.features) != int(args[0].Int()) { + if len(s.features) != args[0].Int() { err = fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.features)) } return } +func (s *suiteFeature) numScenariosRegistered(args ...Arg) (err error) { + var num int + for _, ft := range s.features { + num += len(ft.Scenarios) + } + if num != args[0].Int() { + err = fmt.Errorf("expected %d scenarios to be registered, but got %d", args[0].Int(), num) + } + return +} + func SuiteContext(g Suite) { s := &suiteFeature{ suite: suite{}, } + g.BeforeScenario(s) + g.Step( regexp.MustCompile(`^a feature path "([^"]*)"$`), StepHandlerFunc(s.featurePath)) @@ -40,4 +60,7 @@ func SuiteContext(g Suite) { g.Step( regexp.MustCompile(`^I should have ([\d]+) features? files?$`), StepHandlerFunc(s.numParsed)) + g.Step( + regexp.MustCompile(`^I should have ([\d]+) scenarios? registered$`), + StepHandlerFunc(s.numScenariosRegistered)) }