add PyString node support for step definitions and arguments

Этот коммит содержится в:
gedi 2015-06-18 10:55:04 +03:00
родитель 0c558cee36
коммит 2807b07739
9 изменённых файлов: 157 добавлений и 91 удалений

Просмотреть файл

@ -3,6 +3,8 @@ package godog
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/DATA-DOG/godog/gherkin"
) )
// Arg is an argument for StepHandler parsed from // Arg is an argument for StepHandler parsed from
@ -15,9 +17,7 @@ type Arg struct {
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Float64() float64 { func (a *Arg) Float64() float64 {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
v, err := strconv.ParseFloat(s, 64) v, err := strconv.ParseFloat(s, 64)
if err == nil { if err == nil {
return v return v
@ -29,9 +29,7 @@ func (a *Arg) Float64() float64 {
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Float32() float32 { func (a *Arg) Float32() float32 {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
v, err := strconv.ParseFloat(s, 32) v, err := strconv.ParseFloat(s, 32)
if err == nil { if err == nil {
return float32(v) return float32(v)
@ -43,9 +41,7 @@ func (a *Arg) Float32() float32 {
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Int() int { func (a *Arg) Int() int {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
v, err := strconv.ParseInt(s, 10, 0) v, err := strconv.ParseInt(s, 10, 0)
if err == nil { if err == nil {
return int(v) return int(v)
@ -57,9 +53,7 @@ func (a *Arg) Int() int {
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Int64() int64 { func (a *Arg) Int64() int64 {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
v, err := strconv.ParseInt(s, 10, 64) v, err := strconv.ParseInt(s, 10, 64)
if err == nil { if err == nil {
return v return v
@ -71,9 +65,7 @@ func (a *Arg) Int64() int64 {
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Int32() int32 { func (a *Arg) Int32() int32 {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
v, err := strconv.ParseInt(s, 10, 32) v, err := strconv.ParseInt(s, 10, 32)
if err == nil { if err == nil {
return int32(v) return int32(v)
@ -85,9 +77,7 @@ func (a *Arg) Int32() int32 {
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Int16() int16 { func (a *Arg) Int16() int16 {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
v, err := strconv.ParseInt(s, 10, 16) v, err := strconv.ParseInt(s, 10, 16)
if err == nil { if err == nil {
return int16(v) return int16(v)
@ -99,9 +89,7 @@ func (a *Arg) Int16() int16 {
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Int8() int8 { func (a *Arg) Int8() int8 {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
v, err := strconv.ParseInt(s, 10, 8) v, err := strconv.ParseInt(s, 10, 8)
if err == nil { if err == nil {
return int8(v) return int8(v)
@ -112,17 +100,26 @@ func (a *Arg) Int8() int8 {
// String converts an argument to string // String converts an argument to string
func (a *Arg) String() string { func (a *Arg) String() string {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
return s return s
} }
// Bytes converts an argument string to bytes // Bytes converts an argument string to bytes
func (a *Arg) Bytes() []byte { func (a *Arg) Bytes() []byte {
s, ok := a.value.(string) s, ok := a.value.(string)
if !ok { a.must(ok, "string")
panic(fmt.Sprintf(`cannot convert "%v" to string`, a.value))
}
return []byte(s) 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))
}
}

Просмотреть файл

@ -6,12 +6,19 @@ Feature: load features
Scenario: load features within path Scenario: load features within path
Given a feature path "features" Given a feature path "features"
When I parse 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 Scenario: load a specific feature file
Given a feature path "features/load_features.feature" Given a feature path "features/load_features.feature"
When I parse features 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 Scenario: load a feature file with a specified scenario
Given a feature path "features/load_features.feature:6" Given a feature path "features/load_features.feature:6"
@ -22,4 +29,8 @@ Feature: load features
Given a feature path "features/load_features.feature" Given a feature path "features/load_features.feature"
And a feature path "features/hooks.feature" And a feature path "features/hooks.feature"
When I parse features 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
"""

Просмотреть файл

@ -20,8 +20,9 @@ type Formatter interface {
// failed represents a failed step data structure // failed represents a failed step data structure
// with all necessary references // with all necessary references
type failed struct { type failed struct {
step *gherkin.Step step *gherkin.Step
err error handler *stepMatchHandler
err error
} }
func (f failed) line() string { func (f failed) line() string {
@ -40,7 +41,8 @@ func (f failed) line() string {
// passed represents a successful step data structure // passed represents a successful step data structure
// with all necessary references // with all necessary references
type passed struct { type passed struct {
step *gherkin.Step step *gherkin.Step
handler *stepMatchHandler
} }
// skipped represents a skipped step data structure // skipped represents a skipped step data structure

Просмотреть файл

@ -153,73 +153,107 @@ func (f *pretty) Summary() {
fmt.Println(elapsed) fmt.Println(elapsed)
} }
// prints a single matched step func (f *pretty) printStep(stepAction interface{}) {
func (f *pretty) printMatchedStep(step *gherkin.Step, match *stepMatchHandler, c color) { var c color
var text string var step *gherkin.Step
if m := (match.expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 { var h *stepMatchHandler
var pos, i int var err error
for pos, i = 0, 0; i < len(m); i++ { var suffix, prefix string
if math.Mod(float64(i), 2) == 0 {
text += cl(step.Text[pos:m[i]], c) switch typ := stepAction.(type) {
} else { case *passed:
text += bcl(step.Text[pos:m[i]], c) step = typ.step
} h = typ.handler
pos = m[i] c = green
} case *failed:
text += cl(step.Text[pos:len(step.Text)], c) step = typ.step
} else { h = typ.handler
text = cl(step.Text, c) 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 if !f.canPrintStep(step) {
name := runtime.FuncForPC(reflect.ValueOf(match.handler).Pointer()).Name() 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 { switch step.Token.Type {
case gherkin.GIVEN: case gherkin.GIVEN:
text = cl("Given", c) + " " + text prefix += cl("Given", c)
case gherkin.WHEN: case gherkin.WHEN:
text = cl("When", c) + " " + text prefix += cl("When", c)
case gherkin.THEN: case gherkin.THEN:
text = cl("Then", c) + " " + text prefix += cl("Then", c)
case gherkin.AND: case gherkin.AND:
text = cl("And", c) + " " + text prefix += cl("And", c)
case gherkin.BUT: 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 // Passed is called to represent a passed step
func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) { func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) {
if f.canPrintStep(step) { s := &passed{step: step, handler: match}
f.printMatchedStep(step, match, green) f.printStep(s)
} f.passed = append(f.passed, s)
f.passed = append(f.passed, &passed{step})
} }
// Skipped is called to represent a passed step // Skipped is called to represent a passed step
func (f *pretty) Skipped(step *gherkin.Step) { func (f *pretty) Skipped(step *gherkin.Step) {
if f.canPrintStep(step) { s := &skipped{step: step}
fmt.Println(cl(step.Token.Text, cyan)) f.printStep(s)
} f.skipped = append(f.skipped, s)
f.skipped = append(f.skipped, &skipped{step})
} }
// Undefined is called to represent a pending step // Undefined is called to represent a pending step
func (f *pretty) Undefined(step *gherkin.Step) { func (f *pretty) Undefined(step *gherkin.Step) {
if f.canPrintStep(step) { s := &undefined{step: step}
fmt.Println(cl(step.Token.Text, yellow)) f.printStep(s)
} f.undefined = append(f.undefined, s)
f.undefined = append(f.undefined, &undefined{step})
} }
// Failed is called to represent a failed step // Failed is called to represent a failed step
func (f *pretty) Failed(step *gherkin.Step, match *stepMatchHandler, err error) { func (f *pretty) Failed(step *gherkin.Step, match *stepMatchHandler, err error) {
if f.canPrintStep(step) { s := &failed{step: step, handler: match, err: err}
f.printMatchedStep(step, match, red) f.printStep(s)
fmt.Println(s(step.Token.Indent) + bcl(err, red)) f.failed = append(f.failed, s)
}
f.failed = append(f.failed, &failed{step, err})
} }

Просмотреть файл

@ -139,8 +139,14 @@ type Feature struct {
// PyString is a multiline text object used with step definition // PyString is a multiline text object used with step definition
type PyString struct { type PyString struct {
*Token *Token
Body string Raw string // raw multiline string body
Step *Step 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 // 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) { func (p *parser) parsePystring() (*PyString, error) {
var tok *Token var tok *Token
started := p.next() // skip the start of pystring 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() { for tok = p.next(); !tok.OfType(EOF, PYSTRING); tok = p.next() {
lines = append(lines, tok.Text) lines = append(lines, tok.Text)
trimmed = append(trimmed, strings.TrimSpace(tok.Text))
} }
if tok.Type == EOF { if tok.Type == EOF {
return nil, fmt.Errorf("pystring which was opened on %s:%d was not closed", p.path, started.Line) return nil, fmt.Errorf("pystring which was opened on %s:%d was not closed", p.path, started.Line)
} }
return &PyString{ return &PyString{
Body: strings.Join(lines, "\n"), Raw: strings.Join(lines, "\n"),
Lines: trimmed,
}, nil }, nil
} }

Просмотреть файл

@ -54,8 +54,8 @@ func (s *Step) assertPyString(text string, t *testing.T) {
if s.PyString == nil { if s.PyString == nil {
t.Fatalf("step '%s %s' has no pystring", s.Type, s.Text) t.Fatalf("step '%s %s' has no pystring", s.Type, s.Text)
} }
if s.PyString.Body != text { if s.PyString.Raw != text {
t.Fatalf("expected step pystring body to be '%s', but got '%s'", text, s.PyString.Body) t.Fatalf("expected step pystring body to be '%s', but got '%s'", text, s.PyString.Raw)
} }
} }

Просмотреть файл

@ -34,4 +34,5 @@ Godog was inspired by Behat and the above description is taken from it's documen
*/ */
package godog package godog
// Version of package - based on Semantic Versioning 2.0.0 http://semver.org/
const Version = "v0.1.0-alpha" const Version = "v0.1.0-alpha"

Просмотреть файл

@ -39,11 +39,24 @@ func (s *suiteFeature) parseFeatures(args ...*Arg) (err error) {
return return
} }
func (s *suiteFeature) numParsed(args ...*Arg) (err error) { func (s *suiteFeature) iShouldHaveNumFeatureFiles(args ...*Arg) error {
if len(s.features) != 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 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 { func (s *suiteFeature) iRunFeatures(args ...*Arg) error {
@ -88,8 +101,8 @@ func SuiteContext(g Suite) {
regexp.MustCompile(`^I parse features$`), regexp.MustCompile(`^I parse features$`),
StepHandlerFunc(s.parseFeatures)) StepHandlerFunc(s.parseFeatures))
g.Step( g.Step(
regexp.MustCompile(`^I should have ([\d]+) features? files?$`), regexp.MustCompile(`^I should have ([\d]+) features? files?:$`),
StepHandlerFunc(s.numParsed)) StepHandlerFunc(s.iShouldHaveNumFeatureFiles))
g.Step( g.Step(
regexp.MustCompile(`^I should have ([\d]+) scenarios? registered$`), regexp.MustCompile(`^I should have ([\d]+) scenarios? registered$`),
StepHandlerFunc(s.numScenariosRegistered)) StepHandlerFunc(s.numScenariosRegistered))

Просмотреть файл

@ -24,17 +24,17 @@ func (f *testFormatter) Node(node interface{}) {
func (f *testFormatter) Summary() {} func (f *testFormatter) Summary() {}
func (f *testFormatter) Passed(step *gherkin.Step, match *stepMatchHandler) { 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) { 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) { 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) { 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})
} }