refactor suite runner to support concurrent model
* ef86715 add a concurrency flag * 8674a58 run suite concurrently, closes #3
Этот коммит содержится в:
родитель
ca36316b7a
коммит
970eddc16a
14 изменённых файлов: 204 добавлений и 102 удалений
|
@ -21,7 +21,7 @@ script:
|
||||||
- go test -race
|
- go test -race
|
||||||
|
|
||||||
# run features
|
# run features
|
||||||
- go run cmd/godog/main.go -f progress
|
- go run cmd/godog/main.go --format=progress --concurrency=4
|
||||||
|
|
||||||
# code correctness
|
# code correctness
|
||||||
- sh -c 'RES="$(go fmt ./...)"; if [ ! -z "$RES" ]; then echo $RES; exit 1; fi'
|
- sh -c 'RES="$(go fmt ./...)"; if [ ! -z "$RES" ]; then echo $RES; exit 1; fi'
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -6,7 +6,7 @@ test:
|
||||||
@golint ./...
|
@golint ./...
|
||||||
go vet ./...
|
go vet ./...
|
||||||
go test
|
go test
|
||||||
go run cmd/godog/main.go -f progress
|
go run cmd/godog/main.go -f progress -c 4
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
@echo "updating all dependencies"
|
@echo "updating all dependencies"
|
||||||
|
|
|
@ -71,6 +71,7 @@ We only need a number of **godogs** for now. Lets define steps.
|
||||||
/* file: examples/godogs/godog.go */
|
/* file: examples/godogs/godog.go */
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
// Godogs to eat
|
||||||
var Godogs int
|
var Godogs int
|
||||||
|
|
||||||
func main() { /* usual main func */ }
|
func main() { /* usual main func */ }
|
||||||
|
@ -146,6 +147,9 @@ See implementation examples:
|
||||||
- changed **godog.Suite** from interface to struct. Context registration should be updated accordingly. The reason
|
- changed **godog.Suite** from interface to struct. Context registration should be updated accordingly. The reason
|
||||||
for change: since it exports the same methods and there is no need to mock a function in tests, there is no
|
for change: since it exports the same methods and there is no need to mock a function in tests, there is no
|
||||||
obvious reason to keep an interface.
|
obvious reason to keep an interface.
|
||||||
|
- in order to support running suite concurrently, needed to refactor an entry point of application. The **Run** method
|
||||||
|
now is a func of godog package which initializes and run the suite (or more suites). Method **New** is removed. This
|
||||||
|
change made godog a little cleaner.
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
|
|
||||||
|
|
11
builder.go
11
builder.go
|
@ -49,11 +49,12 @@ func newBuilder(buildPath string) (*builder, error) {
|
||||||
){{ end }}
|
){{ end }}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
suite := {{ if not .Internal }}godog.{{ end }}New()
|
|
||||||
{{range .Contexts}}
|
{{ if not .Internal }}godog.{{ end }}Run(func (suite *{{ if not .Internal }}godog.{{ end }}Suite) {
|
||||||
{{ . }}(suite)
|
{{range .Contexts}}
|
||||||
{{end}}
|
{{ . }}(suite)
|
||||||
suite.Run()
|
{{end}}
|
||||||
|
})
|
||||||
}`)),
|
}`)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/DATA-DOG/godog"
|
"github.com/DATA-DOG/godog"
|
||||||
"github.com/shiena/ansicolor"
|
"github.com/shiena/ansicolor"
|
||||||
|
@ -12,7 +14,7 @@ func buildAndRun() error {
|
||||||
// will support Ansi colors for windows
|
// will support Ansi colors for windows
|
||||||
stdout := ansicolor.NewAnsiColorWriter(os.Stdout)
|
stdout := ansicolor.NewAnsiColorWriter(os.Stdout)
|
||||||
|
|
||||||
builtFile := "/tmp/bgodog.go"
|
builtFile := fmt.Sprintf("%s/%dgodog.go", os.TempDir(), time.Now().UnixNano())
|
||||||
// @TODO: then there is a suite error or panic, it may
|
// @TODO: then there is a suite error or panic, it may
|
||||||
// be interesting to see the built file. But we
|
// be interesting to see the built file. But we
|
||||||
// even cannot determine the status of exit error
|
// even cannot determine the status of exit error
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
// Godogs to eat
|
||||||
var Godogs int
|
var Godogs int
|
||||||
|
|
||||||
func main() { /* usual main func */ }
|
func main() { /* usual main func */ }
|
||||||
|
|
|
@ -28,7 +28,7 @@ Feature: undefined step snippets
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s godog.Suite) {
|
func featureContext(s *godog.Suite) {
|
||||||
s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, iSendrequestTo)
|
s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, iSendrequestTo)
|
||||||
s.Step(`^the response code should be (\d+)$`, theResponseCodeShouldBe)
|
s.Step(`^the response code should be (\d+)$`, theResponseCodeShouldBe)
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ Feature: undefined step snippets
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
func featureContext(s godog.Suite) {
|
func featureContext(s *godog.Suite) {
|
||||||
s.Step(`^I send "([^"]*)" request to "([^"]*)" with:$`, iSendrequestTowith)
|
s.Step(`^I send "([^"]*)" request to "([^"]*)" with:$`, iSendrequestTowith)
|
||||||
s.Step(`^the response code should be (\d+) and header "([^"]*)" should be "([^"]*)"$`, theResponseCodeShouldBeAndHeadershouldBe)
|
s.Step(`^the response code should be (\d+) and header "([^"]*)" should be "([^"]*)"$`, theResponseCodeShouldBeAndHeadershouldBe)
|
||||||
}
|
}
|
||||||
|
|
29
flags.go
29
flags.go
|
@ -5,18 +5,18 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flags builds a *flag.FlagSet with all flags
|
func flags(format, tags *string, defs, sof, vers *bool, cl *int) *flag.FlagSet {
|
||||||
// required for the godog suite
|
|
||||||
func flags(s *Suite) *flag.FlagSet {
|
|
||||||
set := flag.NewFlagSet("godog", flag.ExitOnError)
|
set := flag.NewFlagSet("godog", flag.ExitOnError)
|
||||||
set.StringVar(&s.format, "format", "pretty", "")
|
set.StringVar(format, "format", "pretty", "")
|
||||||
set.StringVar(&s.format, "f", "pretty", "")
|
set.StringVar(format, "f", "pretty", "")
|
||||||
set.StringVar(&s.tags, "tags", "", "")
|
set.StringVar(tags, "tags", "", "")
|
||||||
set.StringVar(&s.tags, "t", "", "")
|
set.StringVar(tags, "t", "", "")
|
||||||
set.BoolVar(&s.definitions, "definitions", false, "")
|
set.IntVar(cl, "concurrency", 1, "")
|
||||||
set.BoolVar(&s.definitions, "d", false, "")
|
set.IntVar(cl, "c", 1, "")
|
||||||
set.BoolVar(&s.stopOnFailure, "stop-on-failure", false, "")
|
set.BoolVar(defs, "definitions", false, "")
|
||||||
set.BoolVar(&s.version, "version", false, "")
|
set.BoolVar(defs, "d", false, "")
|
||||||
|
set.BoolVar(sof, "stop-on-failure", false, "")
|
||||||
|
set.BoolVar(vers, "version", false, "")
|
||||||
set.Usage = usage
|
set.Usage = usage
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func usage() {
|
||||||
if len(name) > 0 {
|
if len(name) > 0 {
|
||||||
name += ":"
|
name += ":"
|
||||||
}
|
}
|
||||||
return s(2) + cl(name, green) + s(30-len(name)) + desc
|
return s(2) + cl(name, green) + s(22-len(name)) + desc
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GENERAL ---
|
// --- GENERAL ---
|
||||||
|
@ -48,6 +48,11 @@ func usage() {
|
||||||
fmt.Println(cl("Options:", yellow))
|
fmt.Println(cl("Options:", yellow))
|
||||||
// --> step definitions
|
// --> step definitions
|
||||||
fmt.Println(opt("-d, --definitions", "Print all available step definitions."))
|
fmt.Println(opt("-d, --definitions", "Print all available step definitions."))
|
||||||
|
// --> concurrency
|
||||||
|
fmt.Println(opt("-c, --concurrency=1", "Run the test suite with concurrency level:"))
|
||||||
|
fmt.Println(opt("", s(4)+"- "+cl(`= 1`, yellow)+": supports all types of formats."))
|
||||||
|
fmt.Println(opt("", s(4)+"- "+cl(`>= 2`, yellow)+": only supports "+cl("progress", yellow)+". Note, that"))
|
||||||
|
fmt.Println(opt("", s(4)+"your context needs to support parallel execution."))
|
||||||
// --> format
|
// --> format
|
||||||
fmt.Println(opt("-f, --format=pretty", "How to format tests output. Available formats:"))
|
fmt.Println(opt("-f, --format=pretty", "How to format tests output. Available formats:"))
|
||||||
for _, f := range formatters {
|
for _, f := range formatters {
|
||||||
|
|
16
fmt.go
16
fmt.go
|
@ -49,6 +49,22 @@ type registeredFormatter struct {
|
||||||
|
|
||||||
var formatters []*registeredFormatter
|
var formatters []*registeredFormatter
|
||||||
|
|
||||||
|
func findFmt(format string) (f Formatter, err error) {
|
||||||
|
var names []string
|
||||||
|
for _, el := range formatters {
|
||||||
|
if el.name == format {
|
||||||
|
f = el.fmt
|
||||||
|
break
|
||||||
|
}
|
||||||
|
names = append(names, el.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f == nil {
|
||||||
|
err = fmt.Errorf(`unregistered formatter name: "%s", use one of: %s`, format, strings.Join(names, ", "))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterFormatter registers a feature suite output
|
// RegisterFormatter registers a feature suite output
|
||||||
// Formatter as the name and descriptiongiven.
|
// Formatter as the name and descriptiongiven.
|
||||||
// Formatter is used to represent suite output
|
// Formatter is used to represent suite output
|
||||||
|
|
|
@ -3,6 +3,7 @@ package godog
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cucumber/gherkin-go"
|
"github.com/cucumber/gherkin-go"
|
||||||
|
@ -20,10 +21,23 @@ func init() {
|
||||||
|
|
||||||
type progress struct {
|
type progress struct {
|
||||||
basefmt
|
basefmt
|
||||||
|
sync.Mutex
|
||||||
stepsPerRow int
|
stepsPerRow int
|
||||||
steps int
|
steps int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *progress) Node(n interface{}) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
f.basefmt.Node(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *progress) Feature(ft *gherkin.Feature, p string) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
f.basefmt.Feature(ft, p)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *progress) Summary() {
|
func (f *progress) Summary() {
|
||||||
left := math.Mod(float64(f.steps), float64(f.stepsPerRow))
|
left := math.Mod(float64(f.steps), float64(f.stepsPerRow))
|
||||||
if left != 0 {
|
if left != 0 {
|
||||||
|
@ -65,26 +79,36 @@ func (f *progress) step(res *stepResult) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Passed(step *gherkin.Step, match *StepDef) {
|
func (f *progress) Passed(step *gherkin.Step, match *StepDef) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
f.basefmt.Passed(step, match)
|
f.basefmt.Passed(step, match)
|
||||||
f.step(f.passed[len(f.passed)-1])
|
f.step(f.passed[len(f.passed)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Skipped(step *gherkin.Step) {
|
func (f *progress) Skipped(step *gherkin.Step) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
f.basefmt.Skipped(step)
|
f.basefmt.Skipped(step)
|
||||||
f.step(f.skipped[len(f.skipped)-1])
|
f.step(f.skipped[len(f.skipped)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Undefined(step *gherkin.Step) {
|
func (f *progress) Undefined(step *gherkin.Step) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
f.basefmt.Undefined(step)
|
f.basefmt.Undefined(step)
|
||||||
f.step(f.undefined[len(f.undefined)-1])
|
f.step(f.undefined[len(f.undefined)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Failed(step *gherkin.Step, match *StepDef, err error) {
|
func (f *progress) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
f.basefmt.Failed(step, match, err)
|
f.basefmt.Failed(step, match, err)
|
||||||
f.step(f.failed[len(f.failed)-1])
|
f.step(f.failed[len(f.failed)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Pending(step *gherkin.Step, match *StepDef) {
|
func (f *progress) Pending(step *gherkin.Step, match *StepDef) {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
f.basefmt.Pending(step, match)
|
f.basefmt.Pending(step, match)
|
||||||
f.step(f.pending[len(f.pending)-1])
|
f.step(f.pending[len(f.pending)-1])
|
||||||
}
|
}
|
||||||
|
|
104
run.go
Обычный файл
104
run.go
Обычный файл
|
@ -0,0 +1,104 @@
|
||||||
|
package godog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type initializer func(*Suite)
|
||||||
|
|
||||||
|
type runner struct {
|
||||||
|
sync.WaitGroup
|
||||||
|
|
||||||
|
semaphore chan int
|
||||||
|
features []*feature
|
||||||
|
fmt Formatter // needs to support concurrency
|
||||||
|
initializer initializer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) run() (failed bool) {
|
||||||
|
r.Add(len(r.features))
|
||||||
|
for _, ft := range r.features {
|
||||||
|
go func(fail *bool, feat *feature) {
|
||||||
|
r.semaphore <- 1
|
||||||
|
suite := &Suite{
|
||||||
|
fmt: r.fmt,
|
||||||
|
features: []*feature{feat},
|
||||||
|
}
|
||||||
|
r.initializer(suite)
|
||||||
|
suite.run()
|
||||||
|
if suite.failed {
|
||||||
|
*fail = true
|
||||||
|
}
|
||||||
|
<-r.semaphore
|
||||||
|
r.Done()
|
||||||
|
}(&failed, ft)
|
||||||
|
}
|
||||||
|
r.Wait()
|
||||||
|
|
||||||
|
r.fmt.Summary()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run creates and runs the feature suite.
|
||||||
|
// uses contextInitializer to register contexts
|
||||||
|
//
|
||||||
|
// the concurrency option allows runner to
|
||||||
|
// initialize a number of suites to be run
|
||||||
|
// separately. Only progress formatter
|
||||||
|
// is supported when concurrency level is
|
||||||
|
// higher than 1
|
||||||
|
//
|
||||||
|
// contextInitializer must be able to register
|
||||||
|
// the step definitions and event handlers.
|
||||||
|
func Run(contextInitializer func(suite *Suite)) {
|
||||||
|
var vers, defs, sof bool
|
||||||
|
var tags, format string
|
||||||
|
var concurrency int
|
||||||
|
flagSet := flags(&format, &tags, &defs, &sof, &vers, &concurrency)
|
||||||
|
err := flagSet.Parse(os.Args[1:])
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case vers:
|
||||||
|
fmt.Println(cl("Godog", green) + " version is " + cl(Version, yellow))
|
||||||
|
return
|
||||||
|
case defs:
|
||||||
|
s := &Suite{}
|
||||||
|
contextInitializer(s)
|
||||||
|
s.printStepDefinitions()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := flagSet.Args()
|
||||||
|
if len(paths) == 0 {
|
||||||
|
inf, err := os.Stat("features")
|
||||||
|
if err == nil && inf.IsDir() {
|
||||||
|
paths = []string{"features"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if concurrency > 1 && format != "progress" {
|
||||||
|
fatal(fmt.Errorf("when concurrency level is higher than 1, only progress format is supported"))
|
||||||
|
}
|
||||||
|
if concurrency > 1 && sof {
|
||||||
|
fatal(fmt.Errorf("when concurrency level is higher than 1, cannot stop on first failure for now"))
|
||||||
|
}
|
||||||
|
formatter, err := findFmt(format)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
features, err := parseFeatures(tags, paths)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
r := runner{
|
||||||
|
fmt: formatter,
|
||||||
|
initializer: contextInitializer,
|
||||||
|
semaphore: make(chan int, concurrency),
|
||||||
|
features: features,
|
||||||
|
}
|
||||||
|
|
||||||
|
if failed := r.run(); failed {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
88
suite.go
88
suite.go
|
@ -44,15 +44,8 @@ type Suite struct {
|
||||||
features []*feature
|
features []*feature
|
||||||
fmt Formatter
|
fmt Formatter
|
||||||
|
|
||||||
failed bool
|
failed bool
|
||||||
|
|
||||||
// options
|
|
||||||
paths []string
|
|
||||||
format string
|
|
||||||
tags string
|
|
||||||
definitions bool
|
|
||||||
stopOnFailure bool
|
stopOnFailure bool
|
||||||
version bool
|
|
||||||
|
|
||||||
// suite event handlers
|
// suite event handlers
|
||||||
beforeSuiteHandlers []func()
|
beforeSuiteHandlers []func()
|
||||||
|
@ -63,12 +56,6 @@ type Suite struct {
|
||||||
afterSuiteHandlers []func()
|
afterSuiteHandlers []func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes a Suite. The instance is passed around
|
|
||||||
// to all context initialization functions from *_test.go files.
|
|
||||||
func New() *Suite {
|
|
||||||
return &Suite{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step allows to register a *StepDef in Godog
|
// Step allows to register a *StepDef in Godog
|
||||||
// feature suite, the definition will be applied
|
// feature suite, the definition will be applied
|
||||||
// to all steps matching the given Regexp expr.
|
// to all steps matching the given Regexp expr.
|
||||||
|
@ -172,52 +159,6 @@ func (s *Suite) AfterSuite(f func()) {
|
||||||
s.afterSuiteHandlers = append(s.afterSuiteHandlers, f)
|
s.afterSuiteHandlers = append(s.afterSuiteHandlers, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the Godog feature suite
|
|
||||||
func (s *Suite) Run() {
|
|
||||||
flagSet := flags(s)
|
|
||||||
fatal(flagSet.Parse(os.Args[1:]))
|
|
||||||
|
|
||||||
s.paths = flagSet.Args()
|
|
||||||
// check the default path
|
|
||||||
if len(s.paths) == 0 {
|
|
||||||
inf, err := os.Stat("features")
|
|
||||||
if err == nil && inf.IsDir() {
|
|
||||||
s.paths = []string{"features"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// validate formatter
|
|
||||||
var names []string
|
|
||||||
for _, f := range formatters {
|
|
||||||
if f.name == s.format {
|
|
||||||
s.fmt = f.fmt
|
|
||||||
break
|
|
||||||
}
|
|
||||||
names = append(names, f.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.fmt == nil {
|
|
||||||
fatal(fmt.Errorf(`unregistered formatter name: "%s", use one of: %s`, s.format, strings.Join(names, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we need to just show something first
|
|
||||||
switch {
|
|
||||||
case s.version:
|
|
||||||
fmt.Println(cl("Godog", green) + " version is " + cl(Version, yellow))
|
|
||||||
return
|
|
||||||
case s.definitions:
|
|
||||||
s.printStepDefinitions()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fatal(s.parseFeatures())
|
|
||||||
// run a feature suite
|
|
||||||
s.run()
|
|
||||||
|
|
||||||
if s.failed {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) run() {
|
func (s *Suite) run() {
|
||||||
// run before suite handlers
|
// run before suite handlers
|
||||||
for _, f := range s.beforeSuiteHandlers {
|
for _, f := range s.beforeSuiteHandlers {
|
||||||
|
@ -235,7 +176,6 @@ func (s *Suite) run() {
|
||||||
for _, f := range s.afterSuiteHandlers {
|
for _, f := range s.afterSuiteHandlers {
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
s.fmt.Summary()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
||||||
|
@ -434,8 +374,8 @@ func (s *Suite) printStepDefinitions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) parseFeatures() (err error) {
|
func parseFeatures(filter string, paths []string) (features []*feature, err error) {
|
||||||
for _, pat := range s.paths {
|
for _, pat := range paths {
|
||||||
// check if line number is specified
|
// check if line number is specified
|
||||||
parts := strings.Split(pat, ":")
|
parts := strings.Split(pat, ":")
|
||||||
path := parts[0]
|
path := parts[0]
|
||||||
|
@ -443,7 +383,7 @@ func (s *Suite) parseFeatures() (err error) {
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
line, err = strconv.Atoi(parts[1])
|
line, err = strconv.Atoi(parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("line number should follow after colon path delimiter")
|
return features, fmt.Errorf("line number should follow after colon path delimiter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// parse features
|
// parse features
|
||||||
|
@ -458,7 +398,7 @@ func (s *Suite) parseFeatures() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.features = append(s.features, &feature{Path: p, Feature: ft})
|
features = append(features, &feature{Path: p, Feature: ft})
|
||||||
// filter scenario by line number
|
// filter scenario by line number
|
||||||
if line != -1 {
|
if line != -1 {
|
||||||
var scenarios []interface{}
|
var scenarios []interface{}
|
||||||
|
@ -477,31 +417,31 @@ func (s *Suite) parseFeatures() (err error) {
|
||||||
}
|
}
|
||||||
ft.ScenarioDefinitions = scenarios
|
ft.ScenarioDefinitions = scenarios
|
||||||
}
|
}
|
||||||
s.applyTagFilter(ft)
|
applyTagFilter(filter, ft)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
// check error
|
// check error
|
||||||
switch {
|
switch {
|
||||||
case os.IsNotExist(err):
|
case os.IsNotExist(err):
|
||||||
return fmt.Errorf(`feature path "%s" is not available`, path)
|
return features, fmt.Errorf(`feature path "%s" is not available`, path)
|
||||||
case os.IsPermission(err):
|
case os.IsPermission(err):
|
||||||
return fmt.Errorf(`feature path "%s" is not accessible`, path)
|
return features, fmt.Errorf(`feature path "%s" is not accessible`, path)
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return err
|
return features, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) applyTagFilter(ft *gherkin.Feature) {
|
func applyTagFilter(tags string, ft *gherkin.Feature) {
|
||||||
if len(s.tags) == 0 {
|
if len(tags) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var scenarios []interface{}
|
var scenarios []interface{}
|
||||||
for _, scenario := range ft.ScenarioDefinitions {
|
for _, scenario := range ft.ScenarioDefinitions {
|
||||||
if s.matchesTags(allTags(ft, scenario)) {
|
if matchesTags(tags, allTags(ft, scenario)) {
|
||||||
scenarios = append(scenarios, scenario)
|
scenarios = append(scenarios, scenario)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -554,9 +494,9 @@ func hasTag(tags []string, tag string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on http://behat.readthedocs.org/en/v2.5/guides/6.cli.html#gherkin-filters
|
// based on http://behat.readthedocs.org/en/v2.5/guides/6.cli.html#gherkin-filters
|
||||||
func (s *Suite) matchesTags(tags []string) (ok bool) {
|
func matchesTags(filter string, tags []string) (ok bool) {
|
||||||
ok = true
|
ok = true
|
||||||
for _, andTags := range strings.Split(s.tags, "&&") {
|
for _, andTags := range strings.Split(filter, "&&") {
|
||||||
var okComma bool
|
var okComma bool
|
||||||
for _, tag := range strings.Split(andTags, ",") {
|
for _, tag := range strings.Split(andTags, ",") {
|
||||||
tag = strings.Replace(strings.TrimSpace(tag), "@", "", -1)
|
tag = strings.Replace(strings.TrimSpace(tag), "@", "", -1)
|
||||||
|
|
|
@ -50,6 +50,7 @@ type firedEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type suiteContext struct {
|
type suiteContext struct {
|
||||||
|
paths []string
|
||||||
testedSuite *Suite
|
testedSuite *Suite
|
||||||
events []*firedEvent
|
events []*firedEvent
|
||||||
fmt *testFormatter
|
fmt *testFormatter
|
||||||
|
@ -58,6 +59,7 @@ type suiteContext struct {
|
||||||
func (s *suiteContext) ResetBeforeEachScenario(interface{}) {
|
func (s *suiteContext) ResetBeforeEachScenario(interface{}) {
|
||||||
// reset whole suite with the state
|
// reset whole suite with the state
|
||||||
s.fmt = &testFormatter{}
|
s.fmt = &testFormatter{}
|
||||||
|
s.paths = []string{}
|
||||||
s.testedSuite = &Suite{fmt: s.fmt}
|
s.testedSuite = &Suite{fmt: s.fmt}
|
||||||
// our tested suite will have the same context registered
|
// our tested suite will have the same context registered
|
||||||
SuiteContext(s.testedSuite)
|
SuiteContext(s.testedSuite)
|
||||||
|
@ -177,12 +179,17 @@ func (s *suiteContext) aFeatureFile(name string, body *gherkin.DocString) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) featurePath(path string) error {
|
func (s *suiteContext) featurePath(path string) error {
|
||||||
s.testedSuite.paths = append(s.testedSuite.paths, path)
|
s.paths = append(s.paths, path)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) parseFeatures() error {
|
func (s *suiteContext) parseFeatures() error {
|
||||||
return s.testedSuite.parseFeatures()
|
fts, err := parseFeatures("", s.paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.testedSuite.features = append(s.testedSuite.features, fts...)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) theSuiteShouldHave(state string) error {
|
func (s *suiteContext) theSuiteShouldHave(state string) error {
|
||||||
|
|
|
@ -5,15 +5,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertNotMatchesTagFilter(tags []string, filter string, t *testing.T) {
|
func assertNotMatchesTagFilter(tags []string, filter string, t *testing.T) {
|
||||||
s := &Suite{tags: filter}
|
if matchesTags(filter, tags) {
|
||||||
if s.matchesTags(tags) {
|
|
||||||
t.Errorf(`expected tags: %v not to match tag filter "%s", but it did`, tags, filter)
|
t.Errorf(`expected tags: %v not to match tag filter "%s", but it did`, tags, filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertMatchesTagFilter(tags []string, filter string, t *testing.T) {
|
func assertMatchesTagFilter(tags []string, filter string, t *testing.T) {
|
||||||
s := &Suite{tags: filter}
|
if !matchesTags(filter, tags) {
|
||||||
if !s.matchesTags(tags) {
|
|
||||||
t.Errorf(`expected tags: %v to match tag filter "%s", but it did not`, tags, filter)
|
t.Errorf(`expected tags: %v to match tag filter "%s", but it did not`, tags, filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче