Merge pull request #191 from funvit/issue-161

fix for progress formatter summary in concurrency mode
Этот коммит содержится в:
Gediminas Morkevicius 2019-12-20 10:11:39 +01:00 коммит произвёл GitHub
родитель aa2f3524d0 652eedf03a
коммит f13252e24e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 125 добавлений и 26 удалений

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

@ -15,6 +15,7 @@ func init() {
} }
func progressFunc(suite string, out io.Writer) Formatter { func progressFunc(suite string, out io.Writer) Formatter {
steps := 0
return &progress{ return &progress{
basefmt: basefmt{ basefmt: basefmt{
started: timeNowFunc(), started: timeNowFunc(),
@ -22,35 +23,37 @@ func progressFunc(suite string, out io.Writer) Formatter {
out: out, out: out,
}, },
stepsPerRow: 70, stepsPerRow: 70,
lock: new(sync.Mutex),
steps: &steps,
} }
} }
type progress struct { type progress struct {
basefmt basefmt
sync.Mutex lock *sync.Mutex
stepsPerRow int stepsPerRow int
steps int steps *int
} }
func (f *progress) Node(n interface{}) { func (f *progress) Node(n interface{}) {
f.Lock() f.lock.Lock()
defer f.Unlock() defer f.lock.Unlock()
f.basefmt.Node(n) f.basefmt.Node(n)
} }
func (f *progress) Feature(ft *gherkin.Feature, p string, c []byte) { func (f *progress) Feature(ft *gherkin.Feature, p string, c []byte) {
f.Lock() f.lock.Lock()
defer f.Unlock() defer f.lock.Unlock()
f.basefmt.Feature(ft, p, c) f.basefmt.Feature(ft, p, c)
} }
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 {
if f.steps > f.stepsPerRow { if *f.steps > f.stepsPerRow {
fmt.Fprintf(f.out, s(f.stepsPerRow-int(left))+fmt.Sprintf(" %d\n", f.steps)) fmt.Fprintf(f.out, s(f.stepsPerRow-int(left))+fmt.Sprintf(" %d\n", *f.steps))
} else { } else {
fmt.Fprintf(f.out, " %d\n", f.steps) fmt.Fprintf(f.out, " %d\n", *f.steps)
} }
} }
fmt.Fprintln(f.out, "") fmt.Fprintln(f.out, "")
@ -79,43 +82,43 @@ func (f *progress) step(res *stepResult) {
case pending: case pending:
fmt.Fprint(f.out, yellow("P")) fmt.Fprint(f.out, yellow("P"))
} }
f.steps++ *f.steps++
if math.Mod(float64(f.steps), float64(f.stepsPerRow)) == 0 { if math.Mod(float64(*f.steps), float64(f.stepsPerRow)) == 0 {
fmt.Fprintf(f.out, " %d\n", f.steps) fmt.Fprintf(f.out, " %d\n", *f.steps)
} }
} }
func (f *progress) Passed(step *gherkin.Step, match *StepDef) { func (f *progress) Passed(step *gherkin.Step, match *StepDef) {
f.Lock() f.lock.Lock()
defer f.Unlock() defer f.lock.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, match *StepDef) { func (f *progress) Skipped(step *gherkin.Step, match *StepDef) {
f.Lock() f.lock.Lock()
defer f.Unlock() defer f.lock.Unlock()
f.basefmt.Skipped(step, match) f.basefmt.Skipped(step, match)
f.step(f.skipped[len(f.skipped)-1]) f.step(f.skipped[len(f.skipped)-1])
} }
func (f *progress) Undefined(step *gherkin.Step, match *StepDef) { func (f *progress) Undefined(step *gherkin.Step, match *StepDef) {
f.Lock() f.lock.Lock()
defer f.Unlock() defer f.lock.Unlock()
f.basefmt.Undefined(step, match) f.basefmt.Undefined(step, match)
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() f.lock.Lock()
defer f.Unlock() defer f.lock.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() f.lock.Lock()
defer f.Unlock() defer f.lock.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])
} }

65
run.go
Просмотреть файл

@ -8,6 +8,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/DATA-DOG/godog/colors" "github.com/DATA-DOG/godog/colors"
) )
@ -28,11 +29,22 @@ type runner struct {
initializer initializer initializer initializer
} }
func (r *runner) concurrent(rate int) (failed bool) { func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool) {
var useFmtCopy bool
var copyLock sync.Mutex
// special mode for progress-formatter
if _, ok := r.fmt.(*progress); ok {
useFmtCopy = true
}
queue := make(chan int, rate) queue := make(chan int, rate)
for i, ft := range r.features { for i, ft := range r.features {
queue <- i // reserve space in queue queue <- i // reserve space in queue
ft := *ft
go func(fail *bool, feat *feature) { go func(fail *bool, feat *feature) {
var fmtCopy Formatter
defer func() { defer func() {
<-queue // free a space in queue <-queue // free a space in queue
}() }()
@ -46,12 +58,59 @@ func (r *runner) concurrent(rate int) (failed bool) {
strict: r.strict, strict: r.strict,
features: []*feature{feat}, features: []*feature{feat},
} }
if useFmtCopy {
fmtCopy = formatterFn()
suite.fmt = fmtCopy
// sync lock and steps for progress printing
if sf, ok := suite.fmt.(*progress); ok {
if rf, ok := r.fmt.(*progress); ok {
sf.lock = rf.lock
sf.steps = rf.steps
}
}
} else {
suite.fmt = r.fmt
}
r.initializer(suite) r.initializer(suite)
suite.run() suite.run()
if suite.failed { if suite.failed {
*fail = true *fail = true
} }
}(&failed, ft) if useFmtCopy {
copyLock.Lock()
dest, dOk := r.fmt.(*progress)
source, sOk := fmtCopy.(*progress)
if dOk && sOk {
for _, v := range source.features {
dest.features = append(dest.features, v)
}
for _, v := range source.failed {
dest.failed = append(dest.failed, v)
}
for _, v := range source.passed {
dest.passed = append(dest.passed, v)
}
for _, v := range source.skipped {
dest.skipped = append(dest.skipped, v)
}
for _, v := range source.undefined {
dest.undefined = append(dest.undefined, v)
}
for _, v := range source.pending {
dest.pending = append(dest.pending, v)
}
} else if !dOk {
panic("cant cast dest formatter to progress-typed")
} else if !sOk {
panic("cant cast source formatter to progress-typed")
}
copyLock.Unlock()
}
}(&failed, &ft)
} }
// wait until last are processed // wait until last are processed
for i := 0; i < rate; i++ { for i := 0; i < rate; i++ {
@ -168,7 +227,7 @@ func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Opt
var failed bool var failed bool
if opt.Concurrency > 1 { if opt.Concurrency > 1 {
failed = r.concurrent(opt.Concurrency) failed = r.concurrent(opt.Concurrency, func() Formatter { return formatter(suite, output) })
} else { } else {
failed = r.run() failed = r.run()
} }

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

@ -275,3 +275,40 @@ func TestFeatureFilePathParser(t *testing.T) {
} }
} }
} }
func TestSucceedWithConcurrencyOption(t *testing.T) {
output := new(bytes.Buffer)
opt := Options{
Format: "progress",
NoColors: true,
Paths: []string{"features"},
Concurrency: 2,
Output: output,
}
expectedOutput := `...................................................................... 70
...................................................................... 140
...................................................................... 210
....................................... 249
60 scenarios (60 passed)
249 steps (249 passed)
0s`
status := RunWithOptions("succeed", func(s *Suite) { SuiteContext(s) }, opt)
if status != exitSuccess {
t.Fatalf("expected exit status to be 0, but was: %d", status)
}
b, err := ioutil.ReadAll(output)
if err != nil {
t.Fatal(err)
}
out := strings.TrimSpace(string(b))
if out != expectedOutput {
t.Fatalf("unexpected output: \"%s\"", out)
}
}