support scenario outline with example table

* 042235c expose StepDef which is needed to formatters
* e9474dc fix outline scenario formatting
* f02c6ce fix comment position calculation for outlines
* 5d7f128 remove step status type, use standard error
Этот коммит содержится в:
gedi 2015-06-19 13:55:27 +03:00
родитель 2e696381c9
коммит df26aa1c1c
12 изменённых файлов: 481 добавлений и 291 удалений

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

@ -56,34 +56,34 @@ func (f BeforeStepHandlerFunc) HandleBeforeStep(step *gherkin.Step) {
// in Suite to be executed after every step
// which will be run
type AfterStepHandler interface {
HandleAfterStep(step *gherkin.Step, status Status)
HandleAfterStep(step *gherkin.Step, err error)
}
// AfterStepHandlerFunc is a function implementing
// AfterStepHandler interface
type AfterStepHandlerFunc func(step *gherkin.Step, status Status)
type AfterStepHandlerFunc func(step *gherkin.Step, err error)
// HandleAfterStep is called with a *gherkin.Step argument
// for after every step which is run by suite
func (f AfterStepHandlerFunc) HandleAfterStep(step *gherkin.Step, status Status) {
f(step, status)
func (f AfterStepHandlerFunc) HandleAfterStep(step *gherkin.Step, err error) {
f(step, err)
}
// AfterScenarioHandler can be registered
// in Suite to be executed after every scenario
// which will be run
type AfterScenarioHandler interface {
HandleAfterScenario(scenario *gherkin.Scenario, status Status)
HandleAfterScenario(scenario *gherkin.Scenario, err error)
}
// AfterScenarioHandlerFunc is a function implementing
// AfterScenarioHandler interface
type AfterScenarioHandlerFunc func(scenario *gherkin.Scenario, status Status)
type AfterScenarioHandlerFunc func(scenario *gherkin.Scenario, err error)
// HandleAfterScenario is called with a *gherkin.Scenario argument
// for after every scenario which is run by suite
func (f AfterScenarioHandlerFunc) HandleAfterScenario(scenario *gherkin.Scenario, status Status) {
f(scenario, status)
func (f AfterScenarioHandlerFunc) HandleAfterScenario(scenario *gherkin.Scenario, err error) {
f(scenario, err)
}
// AfterSuiteHandler can be registered

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

@ -27,8 +27,8 @@ Feature: suite events
When I run feature suite
Then these events had to be fired for a number of times:
| BeforeSuite | 1 |
| BeforeScenario | 4 |
| BeforeStep | 13 |
| AfterStep | 13 |
| AfterScenario | 4 |
| BeforeScenario | 6 |
| BeforeStep | 19 |
| AfterStep | 19 |
| AfterScenario | 6 |
| AfterSuite | 1 |

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

@ -21,10 +21,16 @@ Feature: load features
features/load.feature
"""
Scenario: load a feature file with a specified scenario
Given a feature path "features/load.feature:6"
Scenario Outline: loaded feature should have a number of scenarios
Given a feature path "<feature>"
When I parse features
Then I should have 1 scenario registered
Then I should have <number> scenario registered
Examples:
| feature | number |
| features/load.feature:3 | 0 |
| features/load.feature:6 | 1 |
| features/load.feature | 4 |
Scenario: load a number of feature files
Given a feature path "features/load.feature"

8
fmt.go
Просмотреть файл

@ -10,8 +10,8 @@ import (
// output summary presentation
type Formatter interface {
Node(interface{})
Failed(*gherkin.Step, *stepMatchHandler, error)
Passed(*gherkin.Step, *stepMatchHandler)
Failed(*gherkin.Step, *StepDef, error)
Passed(*gherkin.Step, *StepDef)
Skipped(*gherkin.Step)
Undefined(*gherkin.Step)
Summary()
@ -21,7 +21,7 @@ type Formatter interface {
// with all necessary references
type failed struct {
step *gherkin.Step
handler *stepMatchHandler
def *StepDef
err error
}
@ -42,7 +42,7 @@ func (f failed) line() string {
// with all necessary references
type passed struct {
step *gherkin.Step
handler *stepMatchHandler
def *StepDef
}
// skipped represents a skipped step data structure

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

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"reflect"
"regexp"
"runtime"
"strings"
"time"
@ -17,6 +18,8 @@ func init() {
})
}
var outlinePlaceholderRegexp *regexp.Regexp = regexp.MustCompile("<[^>]+>")
// a built in default pretty formatter
type pretty struct {
feature *gherkin.Feature
@ -25,6 +28,11 @@ type pretty struct {
background *gherkin.Background
scenario *gherkin.Scenario
// outline
outlineExamples int
outlineNumSteps int
outlineSteps []interface{}
// summary
started time.Time
features []*gherkin.Feature
@ -51,33 +59,31 @@ func (f *pretty) Node(node interface{}) {
f.scenario = nil
f.background = nil
f.features = append(f.features, t)
fmt.Println(bcl("Feature: ", white) + t.Title)
fmt.Println(bcl(t.Token.Keyword+": ", white) + t.Title)
fmt.Println(t.Description)
case *gherkin.Background:
// do not repeat background for the same feature
if f.background == nil && f.scenario == nil {
f.background = t
// determine comment position based on step length
f.commentPos = len(t.Token.Text)
for _, step := range t.Steps {
if len(step.Token.Text) > f.commentPos {
f.commentPos = len(step.Token.Text)
}
}
f.commentPos = longestStep(t.Steps, t.Token.Length())
// print background node
fmt.Println("\n" + s(t.Token.Indent) + bcl("Background:", white))
fmt.Println("\n" + s(t.Token.Indent) + bcl(t.Token.Keyword+":", white))
}
case *gherkin.Scenario:
f.scenario = t
// determine comment position based on step length
f.commentPos = len(t.Token.Text)
for _, step := range t.Steps {
if len(step.Token.Text) > f.commentPos {
f.commentPos = len(step.Token.Text)
f.commentPos = longestStep(t.Steps, t.Token.Length())
if t.Outline != nil {
f.outlineSteps = []interface{}{} // reset steps list
f.commentPos = longestStep(t.Outline.Steps, t.Token.Length())
if f.outlineExamples == 0 {
f.outlineNumSteps = len(t.Outline.Steps)
f.outlineExamples = len(t.Outline.Examples.Rows) - 1
} else {
return // already printed an outline
}
}
text := s(t.Token.Indent) + bcl("Scenario: ", white) + t.Title
text += s(f.commentPos-len(t.Token.Text)+1) + f.line(t.Token)
text := s(t.Token.Indent) + bcl(t.Token.Keyword+": ", white) + t.Title
text += s(f.commentPos-t.Token.Length()+1) + f.line(t.Token)
fmt.Println("\n" + text)
}
}
@ -93,8 +99,22 @@ func (f *pretty) Summary() {
}
if len(failedScenarios) > 0 {
fmt.Println("\n--- " + cl("Failed scenarios:", red) + "\n")
var unique []string
for _, fail := range failedScenarios {
fmt.Println(" " + cl(fail.line(), red))
var found bool
for _, in := range unique {
if in == fail.line() {
found = true
break
}
}
if !found {
unique = append(unique, fail.line())
}
}
for _, fail := range unique {
fmt.Println(" " + cl(fail, red))
}
}
var total, passed int
@ -142,21 +162,129 @@ func (f *pretty) Summary() {
fmt.Println(elapsed)
}
func (f *pretty) printStep(stepAction interface{}) {
var c color
var step *gherkin.Step
var h *stepMatchHandler
var err error
var suffix, prefix string
func (f *pretty) printOutlineExample() {
var failed error
clr := green
tbl := f.scenario.Outline.Examples
firstExample := f.outlineExamples == len(tbl.Rows)-1
for i, act := range f.outlineSteps {
var c color
var def *StepDef
var err error
_, def, c, err = f.stepDetails(act)
// determine example row status
switch {
case err != nil:
failed = err
clr = red
case c == yellow:
clr = yellow
case c == cyan && clr == green:
clr = cyan
}
if firstExample {
// in first example, we need to print steps
var text string
ostep := f.scenario.Outline.Steps[i]
if def != nil {
if m := outlinePlaceholderRegexp.FindAllStringIndex(ostep.Text, -1); len(m) > 0 {
var pos int
for i := 0; i < len(m); i++ {
pair := m[i]
text += cl(ostep.Text[pos:pair[0]], cyan)
text += bcl(ostep.Text[pair[0]:pair[1]], cyan)
pos = pair[1]
}
text += cl(ostep.Text[pos:len(ostep.Text)], cyan)
} else {
text = cl(ostep.Text, cyan)
}
// use reflect to get step handler function name
name := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name()
text += s(f.commentPos-ostep.Token.Length()+1) + cl(fmt.Sprintf("# %s", name), black)
} else {
text = cl(ostep.Text, cyan)
}
// print the step outline
fmt.Println(s(ostep.Token.Indent) + cl(ostep.Token.Keyword, cyan) + " " + text)
}
}
cols := make([]string, len(tbl.Rows[0]))
max := longest(tbl)
// an example table header
if firstExample {
out := f.scenario.Outline
fmt.Println("")
fmt.Println(s(out.Token.Indent) + bcl(out.Token.Keyword+":", white))
row := tbl.Rows[0]
for i, col := range row {
cols[i] = cl(col, cyan) + s(max[i]-len(col))
}
fmt.Println(s(tbl.Token.Indent) + "| " + strings.Join(cols, " | ") + " |")
}
// an example table row
row := tbl.Rows[len(tbl.Rows)-f.outlineExamples]
for i, col := range row {
cols[i] = cl(col, clr) + s(max[i]-len(col))
}
fmt.Println(s(tbl.Token.Indent) + "| " + strings.Join(cols, " | ") + " |")
// if there is an error
if failed != nil {
fmt.Println(s(tbl.Token.Indent) + bcl(failed, red))
}
}
func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
text := s(step.Token.Indent) + cl(step.Token.Keyword, c) + " "
switch {
case def != nil:
if m := (def.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)
}
// use reflect to get step handler function name
name := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name()
text += s(f.commentPos-step.Token.Length()+1) + cl(fmt.Sprintf("# %s", name), black)
default:
text += cl(step.Text, c)
}
fmt.Println(text)
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 step.Table != nil {
f.printTable(step.Table, c)
}
}
func (f *pretty) stepDetails(stepAction interface{}) (step *gherkin.Step, def *StepDef, c color, err error) {
switch typ := stepAction.(type) {
case *passed:
step = typ.step
h = typ.handler
def = typ.def
c = green
case *failed:
step = typ.step
h = typ.handler
def = typ.def
err = typ.err
c = red
case *skipped:
@ -168,56 +296,33 @@ func (f *pretty) printStep(stepAction interface{}) {
default:
fatal(fmt.Errorf("unexpected step type received: %T", typ))
}
return
}
func (f *pretty) printStepKind(stepAction interface{}) {
var c color
var step *gherkin.Step
var def *StepDef
var err error
step, def, c, err = f.stepDetails(stepAction)
// do not print background more than once
if f.scenario == nil && step.Background != f.background {
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)
if f.outlineExamples != 0 {
f.outlineSteps = append(f.outlineSteps, stepAction)
if len(f.outlineSteps) == f.outlineNumSteps {
// an outline example steps has went through
f.printOutlineExample()
f.outlineExamples -= 1
}
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)
return // wait till example steps
}
prefix = s(step.Token.Indent)
switch step.Token.Type {
case gherkin.GIVEN:
prefix += cl("Given", c)
case gherkin.WHEN:
prefix += cl("When", c)
case gherkin.THEN:
prefix += cl("Then", c)
case gherkin.AND:
prefix += cl("And", c)
case gherkin.BUT:
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 step.Table != nil {
f.printTable(step.Table, c)
}
f.printStep(step, def, c)
if err != nil {
fmt.Println(s(step.Token.Indent) + bcl(err, red))
}
@ -225,8 +330,47 @@ func (f *pretty) printStep(stepAction interface{}) {
// print table with aligned table cells
func (f *pretty) printTable(t *gherkin.Table, c color) {
var longest = make([]int, len(t.Rows[0]))
var l = longest(t)
var cols = make([]string, len(t.Rows[0]))
for _, row := range t.Rows {
for i, col := range row {
cols[i] = col + s(l[i]-len(col))
}
fmt.Println(s(t.Token.Indent) + cl("| "+strings.Join(cols, " | ")+" |", c))
}
}
// Passed is called to represent a passed step
func (f *pretty) Passed(step *gherkin.Step, match *StepDef) {
s := &passed{step: step, def: match}
f.printStepKind(s)
f.passed = append(f.passed, s)
}
// Skipped is called to represent a passed step
func (f *pretty) Skipped(step *gherkin.Step) {
s := &skipped{step: step}
f.printStepKind(s)
f.skipped = append(f.skipped, s)
}
// Undefined is called to represent a pending step
func (f *pretty) Undefined(step *gherkin.Step) {
s := &undefined{step: step}
f.printStepKind(s)
f.undefined = append(f.undefined, s)
}
// Failed is called to represent a failed step
func (f *pretty) Failed(step *gherkin.Step, match *StepDef, err error) {
s := &failed{step: step, def: match, err: err}
f.printStepKind(s)
f.failed = append(f.failed, s)
}
// longest gives a list of longest columns of all rows in Table
func longest(t *gherkin.Table) []int {
var longest = make([]int, len(t.Rows[0]))
for _, row := range t.Rows {
for i, col := range row {
if longest[i] < len(col) {
@ -234,38 +378,16 @@ func (f *pretty) printTable(t *gherkin.Table, c color) {
}
}
}
for _, row := range t.Rows {
for i, col := range row {
cols[i] = col + s(longest[i]-len(col))
return longest
}
func longestStep(steps []*gherkin.Step, base int) int {
ret := base
for _, step := range steps {
length := step.Token.Length()
if length > base {
ret = length
}
fmt.Println(s(t.Token.Indent) + cl("| "+strings.Join(cols, " | ")+" |", c))
}
}
// Passed is called to represent a passed step
func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) {
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) {
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) {
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) {
s := &failed{step: step, handler: match, err: err}
f.printStep(s)
f.failed = append(f.failed, s)
return ret
}

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

@ -23,8 +23,8 @@ 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: step, handler: match})
func (f *testFormatter) Passed(step *gherkin.Step, match *StepDef) {
f.passed = append(f.passed, &passed{step: step, def: match})
}
func (f *testFormatter) Skipped(step *gherkin.Step) {
@ -35,6 +35,6 @@ func (f *testFormatter) Undefined(step *gherkin.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: step, handler: match, err: err})
func (f *testFormatter) Failed(step *gherkin.Step, match *StepDef, err error) {
f.failed = append(f.failed, &failed{step: step, def: match, err: err})
}

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

@ -88,6 +88,17 @@ func (t Tags) Has(tag Tag) bool {
return false
}
// Outline is a scenario outline with an
// example table. Steps are listed with
// placeholders which are replaced with
// each example table row
type Outline struct {
*Token
Scenario *Scenario
Steps []*Step
Examples *Table
}
// Scenario describes the scenario details
//
// if Examples table is not nil, then it
@ -103,7 +114,7 @@ type Scenario struct {
Title string
Steps []*Step
Tags Tags
Examples *Table
Outline *Outline
Feature *Feature
}
@ -154,7 +165,6 @@ func (p *PyString) String() string {
// step definition or outline scenario
type Table struct {
*Token
OutlineScenario *Scenario
Step *Step
Rows [][]string
}
@ -320,10 +330,18 @@ func (p *parser) parseScenario() (s *Scenario, err error) {
"but got '" + peek.Type.String() + "' instead, for scenario outline examples",
}, " "), examples.Line)
}
if s.Examples, err = p.parseTable(); err != nil {
s.Outline = &Outline{
Token: examples,
Scenario: s,
Steps: s.Steps,
}
s.Steps = []*Step{} // move steps to outline
if s.Outline.Examples, err = p.parseTable(); err != nil {
return s, err
}
s.Examples.OutlineScenario = s
if len(s.Outline.Examples.Rows) < 2 {
return s, p.err("expected an example table to have at least two rows: header and at least one example", examples.Line)
}
}
return s, nil
}

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

@ -21,6 +21,30 @@ var matchers = map[string]*regexp.Regexp{
"table_row": regexp.MustCompile("^(\\s*)\\|([^#]*)(#.*)?"),
}
// for now only english language is supported
var keywords = map[TokenType]string{
// special
ILLEGAL: "Illegal",
EOF: "End of file",
NEW_LINE: "New line",
TAGS: "Tags",
COMMENT: "Comment",
PYSTRING: "PyString",
TABLE_ROW: "Table row",
TEXT: "Text",
// general
GIVEN: "Given",
WHEN: "When",
THEN: "Then",
AND: "And",
BUT: "But",
FEATURE: "Feature",
BACKGROUND: "Background",
SCENARIO: "Scenario",
SCENARIO_OUTLINE: "Scenario Outline",
EXAMPLES: "Examples",
}
type lexer struct {
reader *bufio.Reader
lines int
@ -37,7 +61,8 @@ func (l *lexer) read() *Token {
if err != nil && len(line) == 0 {
return &Token{
Type: EOF,
Line: l.lines,
Line: l.lines + 1,
Keyword: keywords[EOF],
}
}
l.lines++
@ -47,6 +72,7 @@ func (l *lexer) read() *Token {
return &Token{
Type: NEW_LINE,
Line: l.lines,
Keyword: keywords[NEW_LINE],
}
}
// comment
@ -59,6 +85,7 @@ func (l *lexer) read() *Token {
Value: comment,
Text: line,
Comment: comment,
Keyword: keywords[COMMENT],
}
}
// pystring
@ -68,6 +95,7 @@ func (l *lexer) read() *Token {
Indent: len(m[1]),
Line: l.lines,
Text: line,
Keyword: keywords[PYSTRING],
}
}
// step
@ -91,6 +119,7 @@ func (l *lexer) read() *Token {
case "But":
tok.Type = BUT
}
tok.Keyword = keywords[tok.Type]
return tok
}
// scenario
@ -102,6 +131,7 @@ func (l *lexer) read() *Token {
Value: strings.TrimSpace(m[2]),
Text: line,
Comment: strings.Trim(m[3], " #"),
Keyword: keywords[SCENARIO],
}
}
// background
@ -112,6 +142,7 @@ func (l *lexer) read() *Token {
Line: l.lines,
Text: line,
Comment: strings.Trim(m[2], " #"),
Keyword: keywords[BACKGROUND],
}
}
// feature
@ -123,6 +154,7 @@ func (l *lexer) read() *Token {
Value: strings.TrimSpace(m[2]),
Text: line,
Comment: strings.Trim(m[3], " #"),
Keyword: keywords[FEATURE],
}
}
// tags
@ -134,6 +166,7 @@ func (l *lexer) read() *Token {
Value: strings.TrimSpace(m[2]),
Text: line,
Comment: strings.Trim(m[3], " #"),
Keyword: keywords[TAGS],
}
}
// table row
@ -145,6 +178,7 @@ func (l *lexer) read() *Token {
Value: strings.TrimSpace(m[2]),
Text: line,
Comment: strings.Trim(m[3], " #"),
Keyword: keywords[TABLE_ROW],
}
}
// scenario outline
@ -156,6 +190,7 @@ func (l *lexer) read() *Token {
Value: strings.TrimSpace(m[2]),
Text: line,
Comment: strings.Trim(m[3], " #"),
Keyword: keywords[SCENARIO_OUTLINE],
}
}
// examples
@ -166,6 +201,7 @@ func (l *lexer) read() *Token {
Line: l.lines,
Text: line,
Comment: strings.Trim(m[2], " #"),
Keyword: keywords[EXAMPLES],
}
}
// text
@ -176,5 +212,6 @@ func (l *lexer) read() *Token {
Value: text,
Indent: len(line) - len(text),
Text: line,
Keyword: keywords[TEXT],
}
}

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

@ -11,6 +11,16 @@ func (s *Scenario) assertTitle(title string, t *testing.T) {
}
}
func (s *Scenario) assertOutlineStep(text string, t *testing.T) *Step {
for _, stp := range s.Outline.Steps {
if stp.Text == text {
return stp
}
}
t.Fatal("expected scenario '%s' to have step: '%s', but it did not", s.Title, text)
return nil
}
func (s *Scenario) assertStep(text string, t *testing.T) *Step {
for _, stp := range s.Steps {
if stp.Text == text {
@ -22,16 +32,16 @@ func (s *Scenario) assertStep(text string, t *testing.T) *Step {
}
func (s *Scenario) assertExampleRow(t *testing.T, num int, cols ...string) {
if s.Examples == nil {
if s.Outline.Examples == nil {
t.Fatalf("outline scenario '%s' has no examples", s.Title)
}
if len(s.Examples.Rows) <= num {
if len(s.Outline.Examples.Rows) <= num {
t.Fatalf("outline scenario '%s' table has no row: %d", s.Title, num)
}
if len(s.Examples.Rows[num]) != len(cols) {
if len(s.Outline.Examples.Rows[num]) != len(cols) {
t.Fatalf("outline scenario '%s' table row length, does not match expected: %d", s.Title, len(cols))
}
for i, col := range s.Examples.Rows[num] {
for i, col := range s.Outline.Examples.Rows[num] {
if col != cols[i] {
t.Fatalf("outline scenario '%s' table row %d, column %d - value '%s', does not match expected: %s", s.Title, num, i, col, cols[i])
}
@ -64,11 +74,11 @@ func Test_parse_scenario_outline(t *testing.T) {
TABLE_ROW,
}, t)
s.assertStep(`I am in a directory "test"`, t)
s.assertStep(`I have a file named "foo"`, t)
s.assertStep(`I have a file named "bar"`, t)
s.assertStep(`I run "ls" with options "<options>"`, t)
s.assertStep(`I should see "<result>"`, t)
s.assertOutlineStep(`I am in a directory "test"`, t)
s.assertOutlineStep(`I have a file named "foo"`, t)
s.assertOutlineStep(`I have a file named "bar"`, t)
s.assertOutlineStep(`I run "ls" with options "<options>"`, t)
s.assertOutlineStep(`I should see "<result>"`, t)
s.assertExampleRow(t, 0, "options", "result")
s.assertExampleRow(t, 1, "-t", "bar foo")

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

@ -1,29 +1,26 @@
package gherkin
import (
"strings"
"unicode"
)
type TokenType int
const (
ILLEGAL TokenType = iota
specials
COMMENT
NEW_LINE
EOF
elements
TEXT
TAGS
TABLE_ROW
PYSTRING
keywords
FEATURE
BACKGROUND
SCENARIO
SCENARIO_OUTLINE
EXAMPLES
steps
GIVEN
WHEN
THEN
@ -31,54 +28,22 @@ const (
BUT
)
// String gives a string representation of token type
func (t TokenType) String() string {
switch t {
case COMMENT:
return "comment"
case NEW_LINE:
return "new line"
case EOF:
return "end of file"
case TEXT:
return "text"
case TAGS:
return "tags"
case TABLE_ROW:
return "table row"
case PYSTRING:
return "pystring"
case FEATURE:
return "feature"
case BACKGROUND:
return "background"
case SCENARIO:
return "scenario"
case SCENARIO_OUTLINE:
return "scenario outline"
case EXAMPLES:
return "examples"
case GIVEN:
return "given step"
case WHEN:
return "when step"
case THEN:
return "then step"
case AND:
return "and step"
case BUT:
return "but step"
}
return "illegal"
return keywords[t]
}
// Token represents a line in gherkin feature file
type Token struct {
Type TokenType // type of token
Line, Indent int // line and indentation number
Value string // interpreted value
Text string // same text as read
Keyword string // @TODO: the translated keyword
Comment string // a comment
}
// OfType checks whether token is one of types
func (t *Token) OfType(all ...TokenType) bool {
for _, typ := range all {
if typ == t.Type {
@ -87,3 +52,12 @@ func (t *Token) OfType(all ...TokenType) bool {
}
return false
}
// Length gives a token text length with indentation
// and keyword, but without comment
func (t *Token) Length() int {
if pos := strings.Index(t.Text, "#"); pos != -1 {
return len(strings.TrimRightFunc(t.Text[:pos], unicode.IsSpace))
}
return len(t.Text)
}

193
suite.go
Просмотреть файл

@ -7,6 +7,7 @@ import (
"reflect"
"regexp"
"runtime"
"strings"
"github.com/DATA-DOG/godog/gherkin"
)
@ -20,30 +21,6 @@ type Regexp interface{}
// or a step handler
type Handler interface{}
// Status represents a step or scenario status
type Status int
// step or scenario status constants
const (
Invalid Status = iota
Passed
Failed
Undefined
)
// String represents status as string
func (s Status) String() string {
switch s {
case Passed:
return "passed"
case Failed:
return "failed"
case Undefined:
return "undefined"
}
return "invalid"
}
// Objects implementing the StepHandler interface can be
// registered as step definitions in godog
//
@ -70,11 +47,17 @@ func (f StepHandlerFunc) HandleStep(args ...*Arg) error {
return f(args...)
}
var errPending = fmt.Errorf("pending step")
// ErrUndefined is returned in case if step definition was not found
var ErrUndefined = fmt.Errorf("step is undefined")
type stepMatchHandler struct {
handler StepHandler
expr *regexp.Regexp
// StepDef is a registered step definition
// contains a StepHandler, a regexp which
// is used to match a step and Args which
// were matched by last step
type StepDef struct {
Args []*Arg
Handler StepHandler
Expr *regexp.Regexp
}
// Suite is an interface which allows various contexts
@ -91,7 +74,7 @@ type Suite interface {
}
type suite struct {
stepHandlers []*stepMatchHandler
stepHandlers []*StepDef
features []*gherkin.Feature
fmt Formatter
@ -151,9 +134,9 @@ func (s *suite) Step(expr Regexp, h Handler) {
default:
panic(fmt.Sprintf("expecting handler to satisfy StepHandler interface, got type: %T", h))
}
s.stepHandlers = append(s.stepHandlers, &stepMatchHandler{
handler: handler,
expr: regex,
s.stepHandlers = append(s.stepHandlers, &StepDef{
Handler: handler,
Expr: regex,
})
}
@ -243,12 +226,10 @@ func (s *suite) run() {
s.fmt.Summary()
}
func (s *suite) runStep(step *gherkin.Step) (err error) {
var match *stepMatchHandler
var args []*Arg
func (s *suite) matchStep(step *gherkin.Step) *StepDef {
for _, h := range s.stepHandlers {
if m := h.expr.FindStringSubmatch(step.Text); len(m) > 0 {
match = h
if m := h.Expr.FindStringSubmatch(step.Text); len(m) > 0 {
var args []*Arg
for _, a := range m[1:] {
args = append(args, &Arg{value: a})
}
@ -258,12 +239,18 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
if step.PyString != nil {
args = append(args, &Arg{value: step.PyString})
}
break
h.Args = args
return h
}
}
return nil
}
func (s *suite) runStep(step *gherkin.Step) (err error) {
match := s.matchStep(step)
if match == nil {
s.fmt.Undefined(step)
return errPending
return ErrUndefined
}
defer func() {
@ -273,7 +260,7 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
}
}()
if err = match.handler.HandleStep(args...); err != nil {
if err = match.Handler.HandleStep(match.Args...); err != nil {
s.fmt.Failed(step, match, err)
} else {
s.fmt.Passed(step, match)
@ -281,9 +268,9 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
return
}
func (s *suite) runSteps(steps []*gherkin.Step) (st Status) {
func (s *suite) runSteps(steps []*gherkin.Step) (err error) {
for _, step := range steps {
if st == Failed || st == Undefined {
if err != nil {
s.fmt.Skipped(step)
continue
}
@ -293,19 +280,11 @@ func (s *suite) runSteps(steps []*gherkin.Step) (st Status) {
h.HandleBeforeStep(step)
}
err := s.runStep(step)
switch err {
case errPending:
st = Undefined
case nil:
st = Passed
default:
st = Failed
}
err = s.runStep(step)
// run after step handlers
for _, h := range s.afterStepHandlers {
h.HandleAfterStep(step, st)
h.HandleAfterStep(step, err)
}
}
return
@ -317,39 +296,52 @@ func (s *suite) skipSteps(steps []*gherkin.Step) {
}
}
func (s *suite) runOutline(scenario *gherkin.Scenario) (err error) {
placeholders := scenario.Outline.Examples.Rows[0]
examples := scenario.Outline.Examples.Rows[1:]
for _, example := range examples {
var steps []*gherkin.Step
for _, step := range scenario.Outline.Steps {
text := step.Text
for i, placeholder := range placeholders {
text = strings.Replace(text, "<"+placeholder+">", example[i], -1)
}
// clone a step
cloned := &gherkin.Step{
Token: step.Token,
Text: text,
Type: step.Type,
PyString: step.PyString,
Table: step.Table,
Background: step.Background,
Scenario: scenario,
}
steps = append(steps, cloned)
}
// set steps to scenario
scenario.Steps = steps
if err = s.runScenario(scenario); err != nil && err != ErrUndefined {
s.failed = true
if cfg.stopOnFailure {
return
}
}
}
return
}
func (s *suite) runFeature(f *gherkin.Feature) {
s.fmt.Node(f)
for _, scenario := range f.Scenarios {
var status Status
// run before scenario handlers
for _, h := range s.beforeScenarioHandlers {
h.HandleBeforeScenario(scenario)
var err error
// handle scenario outline differently
if scenario.Outline != nil {
err = s.runOutline(scenario)
} else {
err = s.runScenario(scenario)
}
// background
if f.Background != nil {
s.fmt.Node(f.Background)
status = s.runSteps(f.Background.Steps)
}
// scenario
s.fmt.Node(scenario)
switch {
case status == Failed:
s.skipSteps(scenario.Steps)
case status == Undefined:
s.skipSteps(scenario.Steps)
case status == Passed || status == Invalid:
status = s.runSteps(scenario.Steps)
}
// run after scenario handlers
for _, h := range s.afterScenarioHandlers {
h.HandleAfterScenario(scenario, status)
}
if status == Failed {
if err != nil && err != ErrUndefined {
s.failed = true
if cfg.stopOnFailure {
return
@ -358,16 +350,47 @@ func (s *suite) runFeature(f *gherkin.Feature) {
}
}
func (s *suite) runScenario(scenario *gherkin.Scenario) (err error) {
// run before scenario handlers
for _, h := range s.beforeScenarioHandlers {
h.HandleBeforeScenario(scenario)
}
// background
if scenario.Feature.Background != nil {
s.fmt.Node(scenario.Feature.Background)
err = s.runSteps(scenario.Feature.Background.Steps)
}
// scenario
s.fmt.Node(scenario)
switch err {
case ErrUndefined:
s.skipSteps(scenario.Steps)
case nil:
err = s.runSteps(scenario.Steps)
default:
s.skipSteps(scenario.Steps)
}
// run after scenario handlers
for _, h := range s.afterScenarioHandlers {
h.HandleAfterScenario(scenario, err)
}
return
}
func (st *suite) printStepDefinitions() {
var longest int
for _, def := range st.stepHandlers {
if longest < len(def.expr.String()) {
longest = len(def.expr.String())
if longest < len(def.Expr.String()) {
longest = len(def.Expr.String())
}
}
for _, def := range st.stepHandlers {
location := runtime.FuncForPC(reflect.ValueOf(def.handler).Pointer()).Name()
fmt.Println(cl(def.expr.String(), yellow)+s(longest-len(def.expr.String())), cl("# "+location, black))
location := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name()
fmt.Println(cl(def.Expr.String(), yellow)+s(longest-len(def.Expr.String())), cl("# "+location, black))
}
if len(st.stepHandlers) == 0 {
fmt.Println("there were no contexts registered, could not find any step definition..")

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

@ -121,14 +121,14 @@ func (s *suiteContext) iAmListeningToSuiteEvents(args ...*Arg) error {
s.testedSuite.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) {
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
}))
s.testedSuite.AfterScenario(AfterScenarioHandlerFunc(func(scenario *gherkin.Scenario, status Status) {
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, status}})
s.testedSuite.AfterScenario(AfterScenarioHandlerFunc(func(scenario *gherkin.Scenario, err error) {
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, err}})
}))
s.testedSuite.BeforeStep(BeforeStepHandlerFunc(func(step *gherkin.Step) {
s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
}))
s.testedSuite.AfterStep(AfterStepHandlerFunc(func(step *gherkin.Step, status Status) {
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, status}})
s.testedSuite.AfterStep(AfterStepHandlerFunc(func(step *gherkin.Step, err error) {
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, err}})
}))
return nil
}