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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче