Merge pull request #191 from funvit/issue-161
fix for progress formatter summary in concurrency mode
Этот коммит содержится в:
коммит
f13252e24e
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
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()
|
||||||
}
|
}
|
||||||
|
|
37
run_test.go
37
run_test.go
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче