From 2807b0773907277eecb66b0179d9d694762cfd52 Mon Sep 17 00:00:00 2001 From: gedi Date: Thu, 18 Jun 2015 10:55:04 +0300 Subject: [PATCH] add PyString node support for step definitions and arguments --- arguments.go | 51 +++++++------- features/load_features.feature | 17 ++++- formatter.go | 8 ++- formatter_pretty.go | 120 +++++++++++++++++++++------------ gherkin/gherkin.go | 16 +++-- gherkin/steps_test.go | 4 +- godog.go | 1 + suite_test.go | 23 +++++-- utils_test.go | 8 +-- 9 files changed, 157 insertions(+), 91 deletions(-) diff --git a/arguments.go b/arguments.go index fc25f71..a21026e 100644 --- a/arguments.go +++ b/arguments.go @@ -3,6 +3,8 @@ package godog import ( "fmt" "strconv" + + "github.com/DATA-DOG/godog/gherkin" ) // Arg is an argument for StepHandler parsed from @@ -15,9 +17,7 @@ type Arg struct { // or panics if unable to convert it func (a *Arg) Float64() float64 { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") v, err := strconv.ParseFloat(s, 64) if err == nil { return v @@ -29,9 +29,7 @@ func (a *Arg) Float64() float64 { // or panics if unable to convert it func (a *Arg) Float32() float32 { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") v, err := strconv.ParseFloat(s, 32) if err == nil { return float32(v) @@ -43,9 +41,7 @@ func (a *Arg) Float32() float32 { // or panics if unable to convert it func (a *Arg) Int() int { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") v, err := strconv.ParseInt(s, 10, 0) if err == nil { return int(v) @@ -57,9 +53,7 @@ func (a *Arg) Int() int { // or panics if unable to convert it func (a *Arg) Int64() int64 { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") v, err := strconv.ParseInt(s, 10, 64) if err == nil { return v @@ -71,9 +65,7 @@ func (a *Arg) Int64() int64 { // or panics if unable to convert it func (a *Arg) Int32() int32 { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") v, err := strconv.ParseInt(s, 10, 32) if err == nil { return int32(v) @@ -85,9 +77,7 @@ func (a *Arg) Int32() int32 { // or panics if unable to convert it func (a *Arg) Int16() int16 { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") v, err := strconv.ParseInt(s, 10, 16) if err == nil { return int16(v) @@ -99,9 +89,7 @@ func (a *Arg) Int16() int16 { // or panics if unable to convert it func (a *Arg) Int8() int8 { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") v, err := strconv.ParseInt(s, 10, 8) if err == nil { return int8(v) @@ -112,17 +100,26 @@ func (a *Arg) Int8() int8 { // String converts an argument to string func (a *Arg) String() string { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") return s } // Bytes converts an argument string to bytes func (a *Arg) Bytes() []byte { s, ok := a.value.(string) - if !ok { - panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value)) - } + a.must(ok, "string") return []byte(s) } + +// PyString converts an argument gherkin PyString node +func (a *Arg) PyString() *gherkin.PyString { + s, ok := a.value.(*gherkin.PyString) + a.must(ok, "*gherkin.PyString") + return s +} + +func (a *Arg) must(ok bool, expected string) { + if !ok { + panic(fmt.Sprintf(`cannot convert "%v" of type "%T" to type "%s"`, a.value, a.value, expected)) + } +} diff --git a/features/load_features.feature b/features/load_features.feature index 91cfe07..99ebfb6 100644 --- a/features/load_features.feature +++ b/features/load_features.feature @@ -6,12 +6,19 @@ Feature: load features Scenario: load features within path Given a feature path "features" When I parse features - Then I should have 2 feature files + Then I should have 2 feature files: + """ + features/hooks.feature + features/load_features.feature + """ 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 + Then I should have 1 feature file: + """ + features/load_features.feature + """ Scenario: load a feature file with a specified scenario Given a feature path "features/load_features.feature:6" @@ -22,4 +29,8 @@ Feature: load features 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 + Then I should have 2 feature files: + """ + features/load_features.feature + features/hooks.feature + """ diff --git a/formatter.go b/formatter.go index 65b4988..91ce520 100644 --- a/formatter.go +++ b/formatter.go @@ -20,8 +20,9 @@ type Formatter interface { // failed represents a failed step data structure // with all necessary references type failed struct { - step *gherkin.Step - err error + step *gherkin.Step + handler *stepMatchHandler + err error } func (f failed) line() string { @@ -40,7 +41,8 @@ func (f failed) line() string { // passed represents a successful step data structure // with all necessary references type passed struct { - step *gherkin.Step + step *gherkin.Step + handler *stepMatchHandler } // skipped represents a skipped step data structure diff --git a/formatter_pretty.go b/formatter_pretty.go index 4b65fc7..f413a86 100644 --- a/formatter_pretty.go +++ b/formatter_pretty.go @@ -153,73 +153,107 @@ func (f *pretty) Summary() { fmt.Println(elapsed) } -// prints a single matched step -func (f *pretty) printMatchedStep(step *gherkin.Step, match *stepMatchHandler, c color) { - var text string - if m := (match.expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 { - var pos, i int - for pos, i = 0, 0; i < len(m); i++ { - if math.Mod(float64(i), 2) == 0 { - text += cl(step.Text[pos:m[i]], c) - } else { - text += bcl(step.Text[pos:m[i]], c) - } - pos = m[i] - } - text += cl(step.Text[pos:len(step.Text)], c) - } else { - text = cl(step.Text, c) +func (f *pretty) printStep(stepAction interface{}) { + var c color + var step *gherkin.Step + var h *stepMatchHandler + var err error + var suffix, prefix string + + switch typ := stepAction.(type) { + case *passed: + step = typ.step + h = typ.handler + c = green + case *failed: + step = typ.step + h = typ.handler + err = typ.err + c = red + case *skipped: + step = typ.step + c = cyan + case *undefined: + step = typ.step + c = yellow + default: + fatal(fmt.Errorf("unexpected step type received: %T", typ)) } - // use reflect to get step handler function name - name := runtime.FuncForPC(reflect.ValueOf(match.handler).Pointer()).Name() + if !f.canPrintStep(step) { + return + } + if h != nil { + if m := (h.expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 { + var pos, i int + for pos, i = 0, 0; i < len(m); i++ { + if math.Mod(float64(i), 2) == 0 { + suffix += cl(step.Text[pos:m[i]], c) + } else { + suffix += bcl(step.Text[pos:m[i]], c) + } + pos = m[i] + } + suffix += cl(step.Text[pos:len(step.Text)], c) + } else { + suffix = cl(step.Text, c) + } + // use reflect to get step handler function name + name := runtime.FuncForPC(reflect.ValueOf(h.handler).Pointer()).Name() + suffix += s(f.commentPos-len(step.Token.Text)+1) + cl(fmt.Sprintf("# %s", name), black) + } else { + suffix = cl(step.Text, c) + } + + prefix = s(step.Token.Indent) switch step.Token.Type { case gherkin.GIVEN: - text = cl("Given", c) + " " + text + prefix += cl("Given", c) case gherkin.WHEN: - text = cl("When", c) + " " + text + prefix += cl("When", c) case gherkin.THEN: - text = cl("Then", c) + " " + text + prefix += cl("Then", c) case gherkin.AND: - text = cl("And", c) + " " + text + prefix += cl("And", c) case gherkin.BUT: - text = cl("But", c) + " " + text + prefix += cl("But", c) + } + fmt.Println(prefix, suffix) + if step.PyString != nil { + fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c)) + fmt.Println(cl(step.PyString.Raw, c)) + fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c)) + } + if err != nil { + fmt.Println(s(step.Token.Indent) + bcl(err, red)) } - text = s(step.Token.Indent) + text - text += s(f.commentPos-len(step.Token.Text)+1) + cl(fmt.Sprintf("# %s", name), black) - fmt.Println(text) } // Passed is called to represent a passed step func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) { - if f.canPrintStep(step) { - f.printMatchedStep(step, match, green) - } - f.passed = append(f.passed, &passed{step}) + s := &passed{step: step, handler: match} + f.printStep(s) + f.passed = append(f.passed, s) } // Skipped is called to represent a passed step func (f *pretty) Skipped(step *gherkin.Step) { - if f.canPrintStep(step) { - fmt.Println(cl(step.Token.Text, cyan)) - } - f.skipped = append(f.skipped, &skipped{step}) + s := &skipped{step: step} + f.printStep(s) + f.skipped = append(f.skipped, s) } // Undefined is called to represent a pending step func (f *pretty) Undefined(step *gherkin.Step) { - if f.canPrintStep(step) { - fmt.Println(cl(step.Token.Text, yellow)) - } - f.undefined = append(f.undefined, &undefined{step}) + s := &undefined{step: step} + f.printStep(s) + f.undefined = append(f.undefined, s) } // Failed is called to represent a failed step func (f *pretty) Failed(step *gherkin.Step, match *stepMatchHandler, err error) { - if f.canPrintStep(step) { - f.printMatchedStep(step, match, red) - fmt.Println(s(step.Token.Indent) + bcl(err, red)) - } - f.failed = append(f.failed, &failed{step, err}) + s := &failed{step: step, handler: match, err: err} + f.printStep(s) + f.failed = append(f.failed, s) } diff --git a/gherkin/gherkin.go b/gherkin/gherkin.go index 2645f84..4393625 100644 --- a/gherkin/gherkin.go +++ b/gherkin/gherkin.go @@ -139,8 +139,14 @@ type Feature struct { // PyString is a multiline text object used with step definition type PyString struct { *Token - Body string - Step *Step + Raw string // raw multiline string body + Lines []string // trimmed lines + Step *Step +} + +// String returns raw multiline string +func (p *PyString) String() string { + return p.Raw } // Table is a row group object used with step definition @@ -360,15 +366,17 @@ func (p *parser) parseSteps() (steps []*Step, err error) { func (p *parser) parsePystring() (*PyString, error) { var tok *Token started := p.next() // skip the start of pystring - var lines []string + var lines, trimmed []string for tok = p.next(); !tok.OfType(EOF, PYSTRING); tok = p.next() { lines = append(lines, tok.Text) + trimmed = append(trimmed, strings.TrimSpace(tok.Text)) } if tok.Type == EOF { return nil, fmt.Errorf("pystring which was opened on %s:%d was not closed", p.path, started.Line) } return &PyString{ - Body: strings.Join(lines, "\n"), + Raw: strings.Join(lines, "\n"), + Lines: trimmed, }, nil } diff --git a/gherkin/steps_test.go b/gherkin/steps_test.go index 88b798c..4870bdc 100644 --- a/gherkin/steps_test.go +++ b/gherkin/steps_test.go @@ -54,8 +54,8 @@ func (s *Step) assertPyString(text string, t *testing.T) { if s.PyString == nil { t.Fatalf("step '%s %s' has no pystring", s.Type, s.Text) } - if s.PyString.Body != text { - t.Fatalf("expected step pystring body to be '%s', but got '%s'", text, s.PyString.Body) + if s.PyString.Raw != text { + t.Fatalf("expected step pystring body to be '%s', but got '%s'", text, s.PyString.Raw) } } diff --git a/godog.go b/godog.go index 91809df..8cf33ad 100644 --- a/godog.go +++ b/godog.go @@ -34,4 +34,5 @@ Godog was inspired by Behat and the above description is taken from it's documen */ package godog +// Version of package - based on Semantic Versioning 2.0.0 http://semver.org/ const Version = "v0.1.0-alpha" diff --git a/suite_test.go b/suite_test.go index dd1e3ab..23fec0a 100644 --- a/suite_test.go +++ b/suite_test.go @@ -39,11 +39,24 @@ func (s *suiteFeature) parseFeatures(args ...*Arg) (err error) { return } -func (s *suiteFeature) numParsed(args ...*Arg) (err error) { +func (s *suiteFeature) iShouldHaveNumFeatureFiles(args ...*Arg) 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 fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.features)) } - return + expected := args[1].PyString().Lines + var actual []string + for _, ft := range s.features { + actual = append(actual, ft.Path) + } + if len(expected) != len(actual) { + return fmt.Errorf("expected %d feature paths to be parsed, but have %d", len(expected), len(actual)) + } + for i := 0; i < len(expected); i++ { + if expected[i] != actual[i] { + return fmt.Errorf(`expected feature path "%s" at position: %d, does not match actual "%s"`, expected[i], i, actual[i]) + } + } + return nil } func (s *suiteFeature) iRunFeatures(args ...*Arg) error { @@ -88,8 +101,8 @@ func SuiteContext(g Suite) { regexp.MustCompile(`^I parse features$`), StepHandlerFunc(s.parseFeatures)) g.Step( - regexp.MustCompile(`^I should have ([\d]+) features? files?$`), - StepHandlerFunc(s.numParsed)) + regexp.MustCompile(`^I should have ([\d]+) features? files?:$`), + StepHandlerFunc(s.iShouldHaveNumFeatureFiles)) g.Step( regexp.MustCompile(`^I should have ([\d]+) scenarios? registered$`), StepHandlerFunc(s.numScenariosRegistered)) diff --git a/utils_test.go b/utils_test.go index ed35954..60c8dce 100644 --- a/utils_test.go +++ b/utils_test.go @@ -24,17 +24,17 @@ func (f *testFormatter) Node(node interface{}) { func (f *testFormatter) Summary() {} func (f *testFormatter) Passed(step *gherkin.Step, match *stepMatchHandler) { - f.passed = append(f.passed, &passed{step}) + f.passed = append(f.passed, &passed{step: step, handler: match}) } func (f *testFormatter) Skipped(step *gherkin.Step) { - f.skipped = append(f.skipped, &skipped{step}) + f.skipped = append(f.skipped, &skipped{step: step}) } func (f *testFormatter) Undefined(step *gherkin.Step) { - f.undefined = append(f.undefined, &undefined{step}) + f.undefined = append(f.undefined, &undefined{step: step}) } func (f *testFormatter) Failed(step *gherkin.Step, match *stepMatchHandler, err error) { - f.failed = append(f.failed, &failed{step, err}) + f.failed = append(f.failed, &failed{step: step, handler: match, err: err}) }