Files
weave-scope/vendor/github.com/lsegal/gucumber/runner.go
2016-03-23 15:41:37 +00:00

420 lines
8.7 KiB
Go

package gucumber
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/lsegal/gucumber/gherkin"
"github.com/shiena/ansicolor"
)
const (
clrWhite = "0"
clrRed = "31"
clrGreen = "32"
clrYellow = "33"
clrCyan = "36"
txtUnmatchInt = `(\d+)`
txtUnmatchFloat = `(-?\d+(?:\.\d+)?)`
txtUnmatchStr = `"(.+?)"`
)
var (
reUnmatchInt = regexp.MustCompile(txtUnmatchInt)
reUnmatchFloat = regexp.MustCompile(txtUnmatchFloat)
reUnmatchStr = regexp.MustCompile(`(<|").+?("|>)`)
reOutlineVal = regexp.MustCompile(`<(.+?)>`)
)
type Runner struct {
*Context
Features []*gherkin.Feature
Results []RunnerResult
Unmatched []*gherkin.Step
FailCount int
SkipCount int
}
type RunnerResult struct {
*TestingT
*gherkin.Feature
*gherkin.Scenario
}
func (c *Context) RunDir(dir string) (*Runner, error) {
g, _ := filepath.Glob(filepath.Join(dir, "*.feature"))
g2, _ := filepath.Glob(filepath.Join(dir, "**", "*.feature"))
g = append(g, g2...)
runner, err := c.RunFiles(g)
if err != nil {
panic(err)
}
if len(runner.Unmatched) > 0 {
fmt.Println("Some steps were missing, you can add them by using the following step definition stubs: ")
fmt.Println("")
fmt.Print(runner.MissingMatcherStubs())
}
os.Exit(runner.FailCount)
return runner, err
}
func (c *Context) RunFiles(featureFiles []string) (*Runner, error) {
r := Runner{
Context: c,
Features: []*gherkin.Feature{},
Results: []RunnerResult{},
Unmatched: []*gherkin.Step{},
}
for _, file := range featureFiles {
fd, err := os.Open(file)
if err != nil {
return nil, err
}
defer fd.Close()
b, err := ioutil.ReadAll(fd)
if err != nil {
return nil, err
}
fs, err := gherkin.ParseFilename(string(b), file)
if err != nil {
return nil, err
}
for _, f := range fs {
r.Features = append(r.Features, &f)
}
}
r.run()
return &r, nil
}
func (c *Runner) MissingMatcherStubs() string {
var buf bytes.Buffer
matches := map[string]bool{}
buf.WriteString(`import . "github.com/lsegal/gucumber"` + "\n\n")
buf.WriteString("func init() {\n")
for _, m := range c.Unmatched {
numInts, numFloats, numStrs := 1, 1, 1
str, args := m.Text, []string{}
str = reUnmatchInt.ReplaceAllStringFunc(str, func(s string) string {
args = append(args, fmt.Sprintf("i%d int", numInts))
numInts++
return txtUnmatchInt
})
str = reUnmatchFloat.ReplaceAllStringFunc(str, func(s string) string {
args = append(args, fmt.Sprintf("s%d float64", numFloats))
numFloats++
return txtUnmatchFloat
})
str = reUnmatchStr.ReplaceAllStringFunc(str, func(s string) string {
args = append(args, fmt.Sprintf("s%d string", numStrs))
numStrs++
return txtUnmatchStr
})
if len(m.Argument) > 0 {
if m.Argument.IsTabular() {
args = append(args, "table [][]string")
} else {
args = append(args, "data string")
}
}
// Don't duplicate matchers. This is mostly for scenario outlines.
if matches[str] {
continue
}
matches[str] = true
fmt.Fprintf(&buf, "\t%s(`^%s$`, func(%s) {\n\t\tT.Skip() // pending\n\t})\n\n",
m.Type, str, strings.Join(args, ", "))
}
buf.WriteString("}\n")
return buf.String()
}
func (c *Runner) run() {
if c.BeforeAllFilter != nil {
c.BeforeAllFilter()
}
for _, f := range c.Features {
c.runFeature(f)
}
if c.AfterAllFilter != nil {
c.AfterAllFilter()
}
c.line("0;1", "Finished (%d passed, %d failed, %d skipped).\n",
len(c.Results)-c.FailCount-c.SkipCount, c.FailCount, c.SkipCount)
}
func (c *Runner) runFeature(f *gherkin.Feature) {
if !f.FilterMatched(c.Filters...) {
result := false
// if any scenarios match, we will run those
for _, s := range f.Scenarios {
if s.FilterMatched(f, c.Filters...) {
result = true
break
}
}
if !result {
return
}
}
for k, fn := range c.BeforeFilters {
if f.FilterMatched(strings.Split(k, "|")...) {
fn()
}
}
if len(f.Tags) > 0 {
c.line(clrCyan, strings.Join([]string(f.Tags), " "))
}
c.line("0;1", "Feature: %s", f.Title)
if f.Background.Steps != nil {
c.runScenario("Background", f, &f.Background, false)
}
for _, s := range f.Scenarios {
c.runScenario("Scenario", f, &s, false)
}
for k, fn := range c.AfterFilters {
if f.FilterMatched(strings.Split(k, "|")...) {
fn()
}
}
}
func (c *Runner) runScenario(title string, f *gherkin.Feature, s *gherkin.Scenario, isExample bool) {
if !s.FilterMatched(f, c.Filters...) {
return
}
for k, fn := range c.BeforeFilters {
if s.FilterMatched(f, strings.Split(k, "|")...) {
fn()
}
}
if s.Examples != "" { // run scenario outline data
exrows := strings.Split(string(s.Examples), "\n")
c.line(clrCyan, " "+strings.Join([]string(s.Tags), " "))
c.fileLine("0;1", " %s Outline: %s", s.Filename, s.Line, s.LongestLine()+1,
title, s.Title)
for _, step := range s.Steps {
c.fileLine("0;0", " %s %s", step.Filename, step.Line,
s.LongestLine()+1, step.Type, step.Text)
}
c.line(clrWhite, "")
c.line("0;1", " Examples:")
c.line(clrCyan, " %s", exrows[0])
tab := s.Examples.ToTable()
tabmap := tab.ToMap()
for i, rows := 1, len(tab); i < rows; i++ {
other := gherkin.Scenario{
Filename: s.Filename,
Line: s.Line,
Title: s.Title,
Examples: gherkin.StringData(""),
Steps: []gherkin.Step{},
}
for _, step := range s.Steps {
step.Text = reOutlineVal.ReplaceAllStringFunc(step.Text, func(t string) string {
return tabmap[t[1:len(t)-1]][i-1]
})
other.Steps = append(other.Steps, step)
}
fc := c.FailCount
clr := clrGreen
c.runScenario(title, f, &other, true)
if fc != c.FailCount {
clr = clrRed
}
c.line(clr, " %s", exrows[i])
}
c.line(clrWhite, "")
for k, fn := range c.AfterFilters {
if s.FilterMatched(f, strings.Split(k, "|")...) {
fn()
}
}
return
}
t := &TestingT{}
skipping := false
clr := clrGreen
if !isExample {
if len(s.Tags) > 0 {
c.line(clrCyan, " "+strings.Join([]string(s.Tags), " "))
}
c.fileLine("0;1", " %s: %s", s.Filename, s.Line, s.LongestLine(),
title, s.Title)
}
for _, step := range s.Steps {
errCount := len(t.errors)
found := false
if !skipping && !t.Failed() {
done := make(chan bool)
go func() {
defer func() {
c.Results = append(c.Results, RunnerResult{t, f, s})
if t.Skipped() {
c.SkipCount++
skipping = true
clr = clrYellow
} else if t.Failed() {
c.FailCount++
clr = clrRed
}
done <- true
}()
f, err := c.Execute(t, step.Text, string(step.Argument))
if err != nil {
t.Error(err)
}
found = f
if !f {
t.Skip("no match function for step")
}
}()
<-done
}
if skipping && !found {
cstep := step
c.Unmatched = append(c.Unmatched, &cstep)
}
if !isExample {
c.fileLine(clr, " %s %s", step.Filename, step.Line,
s.LongestLine(), step.Type, step.Text)
if len(step.Argument) > 0 {
if !step.Argument.IsTabular() {
c.line(clrWhite, ` """`)
}
for _, l := range strings.Split(string(step.Argument), "\n") {
c.line(clrWhite, " %s", l)
}
if !step.Argument.IsTabular() {
c.line(clrWhite, ` """`)
}
}
}
if len(t.errors) > errCount {
c.line(clrRed, "\n"+t.errors[len(t.errors)-1].message)
}
}
if !isExample {
c.line(clrWhite, "")
}
for k, fn := range c.AfterFilters {
if s.FilterMatched(f, strings.Split(k, "|")...) {
fn()
}
}
}
var writer = ansicolor.NewAnsiColorWriter(os.Stdout)
func (c *Runner) line(clr, text string, args ...interface{}) {
fmt.Fprintf(writer, "\033[%sm%s\033[0;0m\n", clr, fmt.Sprintf(text, args...))
}
func (c *Runner) fileLine(clr, text, filename string, line int, max int, args ...interface{}) {
space, str := "", fmt.Sprintf(text, args...)
if l := max + 5 - len(str); l > 0 {
space = strings.Repeat(" ", l)
}
comment := fmt.Sprintf("%s \033[39;0m# %s:%d", space, filename, line)
c.line(clr, "%s%s", str, comment)
}
type Tester interface {
Errorf(format string, args ...interface{})
Skip(args ...interface{})
}
type TestingT struct {
skipped bool
errors []TestError
}
type TestError struct {
message string
stack []byte
}
func (t *TestingT) Errorf(format string, args ...interface{}) {
var buf bytes.Buffer
str := fmt.Sprintf(format, args...)
sbuf := make([]byte, 8192)
for {
size := runtime.Stack(sbuf, false)
if size < len(sbuf) {
break
}
buf.Write(sbuf[0:size])
}
t.errors = append(t.errors, TestError{message: str, stack: buf.Bytes()})
}
func (t *TestingT) Skip(args ...interface{}) {
t.skipped = true
}
func (t *TestingT) Skipped() bool {
return t.skipped
}
func (t *TestingT) Failed() bool {
return len(t.errors) > 0
}
func (t *TestingT) Error(err error) {
t.errors = append(t.errors, TestError{message: err.Error()})
}