From cb47b270909ffb3ce9f97d6026d76091cb897248 Mon Sep 17 00:00:00 2001 From: gedi Date: Wed, 17 Jun 2015 14:46:15 +0300 Subject: [PATCH] more error details, refactor arguments to interface type --- arguments.go | 90 ++++++++++++++++++++++++++++++------------ config.go | 12 +++++- features/hooks.feature | 8 ++++ formatter.go | 4 ++ gherkin/gherkin.go | 3 ++ gherkin/lexer_test.go | 36 ++++++++--------- suite.go | 16 +++++--- suite_test.go | 8 ++-- 8 files changed, 122 insertions(+), 55 deletions(-) diff --git a/arguments.go b/arguments.go index 8484067..fc25f71 100644 --- a/arguments.go +++ b/arguments.go @@ -7,84 +7,122 @@ import ( // Arg is an argument for StepHandler parsed from // the regexp submatch to handle the step -type Arg string +type Arg struct { + value interface{} +} // Float64 converts an argument to float64 // or panics if unable to convert it -func (a Arg) Float64() float64 { - v, err := strconv.ParseFloat(string(a), 64) +func (a *Arg) Float64() float64 { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + v, err := strconv.ParseFloat(s, 64) if err == nil { return v } - panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, a, err)) + panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, s, err)) } // Float32 converts an argument to float32 // or panics if unable to convert it -func (a Arg) Float32() float32 { - v, err := strconv.ParseFloat(string(a), 32) +func (a *Arg) Float32() float32 { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + v, err := strconv.ParseFloat(s, 32) if err == nil { return float32(v) } - panic(fmt.Sprintf(`cannot convert "%s" to float32: %s`, a, err)) + panic(fmt.Sprintf(`cannot convert "%s" to float32: %s`, s, 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) +func (a *Arg) Int() int { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + v, err := strconv.ParseInt(s, 10, 0) if err == nil { return int(v) } - panic(fmt.Sprintf(`cannot convert "%s" to int: %s`, a, err)) + panic(fmt.Sprintf(`cannot convert "%s" to int: %s`, s, 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) +func (a *Arg) Int64() int64 { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + v, err := strconv.ParseInt(s, 10, 64) if err == nil { return v } - panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, a, err)) + panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, s, 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) +func (a *Arg) Int32() int32 { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + v, err := strconv.ParseInt(s, 10, 32) if err == nil { return int32(v) } - panic(fmt.Sprintf(`cannot convert "%s" to int32: %s`, a, err)) + panic(fmt.Sprintf(`cannot convert "%s" to int32: %s`, s, 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) +func (a *Arg) Int16() int16 { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + v, err := strconv.ParseInt(s, 10, 16) if err == nil { return int16(v) } - panic(fmt.Sprintf(`cannot convert "%s" to int16: %s`, a, err)) + panic(fmt.Sprintf(`cannot convert "%s" to int16: %s`, s, 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) +func (a *Arg) Int8() int8 { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + v, err := strconv.ParseInt(s, 10, 8) if err == nil { return int8(v) } - panic(fmt.Sprintf(`cannot convert "%s" to int8: %s`, a, err)) + panic(fmt.Sprintf(`cannot convert "%s" to int8: %s`, s, err)) } // String converts an argument to string -func (a Arg) String() string { - return string(a) +func (a *Arg) String() string { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + return s } // Bytes converts an argument string to bytes -func (a Arg) Bytes() []byte { - return []byte(a) +func (a *Arg) Bytes() []byte { + s, ok := a.value.(string) + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) + } + return []byte(s) } diff --git a/config.go b/config.go index ed347dc..6cfc82c 100644 --- a/config.go +++ b/config.go @@ -108,6 +108,7 @@ func (c *config) validate() error { func (c *config) features() (lst []*gherkin.Feature, err error) { for _, pat := range c.paths { + // check if line number is specified parts := strings.Split(pat, ":") path := parts[0] line := -1 @@ -117,6 +118,7 @@ func (c *config) features() (lst []*gherkin.Feature, err error) { return lst, fmt.Errorf("line number should follow after colon path delimiter") } } + // parse features err = filepath.Walk(path, func(p string, f os.FileInfo, err error) error { if err == nil && !f.IsDir() && strings.HasSuffix(p, ".feature") { ft, err := gherkin.Parse(p) @@ -142,8 +144,14 @@ func (c *config) features() (lst []*gherkin.Feature, err error) { } return err }) - if err != nil { - return lst, fmt.Errorf(`feature path "%s" is not available or accessible`, path) + // check error + switch { + case os.IsNotExist(err): + return lst, fmt.Errorf(`feature path "%s" is not available`, path) + case os.IsPermission(err): + return lst, fmt.Errorf(`feature path "%s" is not accessible`, path) + case err != nil: + return lst, err } } return diff --git a/features/hooks.feature b/features/hooks.feature index 2eb8b35..4539f27 100644 --- a/features/hooks.feature +++ b/features/hooks.feature @@ -1 +1,9 @@ Feature: suite hooks + In order to run tasks before and after important events + As a test suite + I need to provide a way to hook into these events + + Background: + Given I have a before scenario hook + And a feature path "features/load_features.feature:6" + # When I parse and run features diff --git a/formatter.go b/formatter.go index c17d4a9..8228951 100644 --- a/formatter.go +++ b/formatter.go @@ -99,6 +99,10 @@ func (f *pretty) canPrintStep(step *gherkin.Step) bool { func (f *pretty) Node(node interface{}) { switch t := node.(type) { case *gherkin.Feature: + if f.feature != nil { + // not a first feature, add a newline + fmt.Println("") + } f.feature = t f.doneBackground = false f.background = nil diff --git a/gherkin/gherkin.go b/gherkin/gherkin.go index 307b35c..2645f84 100644 --- a/gherkin/gherkin.go +++ b/gherkin/gherkin.go @@ -281,6 +281,9 @@ func (p *parser) parseFeature() (ft *Feature, err error) { // there must be a scenario or scenario outline otherwise if !tok.OfType(SCENARIO, SCENARIO_OUTLINE) { + if tok.Type == EOF { + return ft, nil // there may not be a scenario defined after background + } return ft, p.err("expected a scenario or scenario outline, but got '"+tok.Type.String()+"' instead", tok.Line) } diff --git a/gherkin/lexer_test.go b/gherkin/lexer_test.go index 48c8606..e207abb 100644 --- a/gherkin/lexer_test.go +++ b/gherkin/lexer_test.go @@ -51,8 +51,8 @@ func Test_feature_read(t *testing.T) { if tok.Value != val { t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value) } - if tok.Line != 0 { - t.Fatalf("Expected a token line to be '0', but got: '%d'", tok.Line) + if tok.Line != 1 { + t.Fatalf("Expected a token line to be '1', but got: '%d'", tok.Line) } if tok.Indent != 0 { t.Fatalf("Expected a token identation to be '0', but got: '%d'", tok.Indent) @@ -66,21 +66,6 @@ func Test_feature_read(t *testing.T) { if tok.Value != val { t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value) } - if tok.Line != 1 { - t.Fatalf("Expected a token line to be '1', but got: '%d'", tok.Line) - } - if tok.Indent != 2 { - t.Fatalf("Expected a token identation to be '2', but got: '%d'", tok.Indent) - } - - tok = l.read() - if tok.Type != TEXT { - t.Fatalf("Expected a 'text' type, but got: '%s'", tok.Type) - } - val = "as gherkin lexer" - if tok.Value != val { - t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value) - } if tok.Line != 2 { t.Fatalf("Expected a token line to be '2', but got: '%d'", tok.Line) } @@ -92,7 +77,7 @@ func Test_feature_read(t *testing.T) { if tok.Type != TEXT { t.Fatalf("Expected a 'text' type, but got: '%s'", tok.Type) } - val = "I need to be able to parse a feature" + val = "as gherkin lexer" if tok.Value != val { t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value) } @@ -103,6 +88,21 @@ func Test_feature_read(t *testing.T) { t.Fatalf("Expected a token identation to be '2', but got: '%d'", tok.Indent) } + tok = l.read() + if tok.Type != TEXT { + t.Fatalf("Expected a 'text' type, but got: '%s'", tok.Type) + } + val = "I need to be able to parse a feature" + if tok.Value != val { + t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value) + } + if tok.Line != 4 { + t.Fatalf("Expected a token line to be '4', but got: '%d'", tok.Line) + } + if tok.Indent != 2 { + t.Fatalf("Expected a token identation to be '2', but got: '%d'", tok.Indent) + } + tok = l.read() if tok.Type != EOF { t.Fatalf("Expected an 'eof' type, but got: '%s'", tok.Type) diff --git a/suite.go b/suite.go index 8c52e1d..cbbf230 100644 --- a/suite.go +++ b/suite.go @@ -30,17 +30,17 @@ func (f BeforeScenarioHandlerFunc) BeforeScenario(scenario *gherkin.Scenario) { // and that the feature runner can move on to the next // step. type StepHandler interface { - HandleStep(args ...Arg) error + HandleStep(args ...*Arg) error } // StepHandlerFunc type is an adapter to allow the use of // ordinary functions as Step handlers. If f is a function // with the appropriate signature, StepHandlerFunc(f) is a // StepHandler object that calls f. -type StepHandlerFunc func(...Arg) error +type StepHandlerFunc func(...*Arg) error // HandleStep calls f(step_arguments...). -func (f StepHandlerFunc) HandleStep(args ...Arg) error { +func (f StepHandlerFunc) HandleStep(args ...*Arg) error { return f(args...) } @@ -113,12 +113,18 @@ func (s *suite) Run() { func (s *suite) runStep(step *gherkin.Step) (err error) { var match *stepMatchHandler - var args []Arg + var args []*Arg for _, h := range s.stepHandlers { if m := h.expr.FindStringSubmatch(step.Text); len(m) > 0 { match = h for _, a := range m[1:] { - args = append(args, Arg(a)) + args = append(args, &Arg{value: a}) + } + if step.Table != nil { + args = append(args, &Arg{value: step.Table}) + } + if step.PyString != nil { + args = append(args, &Arg{value: step.PyString}) } break } diff --git a/suite_test.go b/suite_test.go index a5aedce..f1743e7 100644 --- a/suite_test.go +++ b/suite_test.go @@ -16,24 +16,24 @@ func (s *suiteFeature) BeforeScenario(scenario *gherkin.Scenario) { cfg.paths = []string{} } -func (s *suiteFeature) featurePath(args ...Arg) error { +func (s *suiteFeature) featurePath(args ...*Arg) error { cfg.paths = append(cfg.paths, args[0].String()) return nil } -func (s *suiteFeature) parseFeatures(args ...Arg) (err error) { +func (s *suiteFeature) parseFeatures(args ...*Arg) (err error) { s.features, err = cfg.features() return } -func (s *suiteFeature) numParsed(args ...Arg) (err error) { +func (s *suiteFeature) numParsed(args ...*Arg) (err error) { 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) { +func (s *suiteFeature) numScenariosRegistered(args ...*Arg) (err error) { var num int for _, ft := range s.features { num += len(ft.Scenarios)