add PyString node support for step definitions and arguments
Этот коммит содержится в:
родитель
0c558cee36
коммит
2807b07739
9 изменённых файлов: 157 добавлений и 91 удалений
51
arguments.go
51
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1
godog.go
1
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"
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче