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 (
"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))
}
}

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

@ -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
"""

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

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

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

@ -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 {
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))
}
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 {
text += cl(step.Text[pos:m[i]], c)
suffix += cl(step.Text[pos:m[i]], c)
} else {
text += bcl(step.Text[pos:m[i]], c)
suffix += bcl(step.Text[pos:m[i]], c)
}
pos = m[i]
}
text += cl(step.Text[pos:len(step.Text)], c)
suffix += cl(step.Text[pos:len(step.Text)], c)
} else {
text = cl(step.Text, c)
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)
}
// use reflect to get step handler function name
name := runtime.FuncForPC(reflect.ValueOf(match.handler).Pointer()).Name()
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)
}

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

@ -139,10 +139,16 @@ type Feature struct {
// PyString is a multiline text object used with step definition
type PyString struct {
*Token
Body string
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
type table struct {
*Token
@ -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
}

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

@ -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)
}
}

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

@ -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"

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

@ -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))

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

@ -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})
}