Broke out some code from massive files into new files
Этот коммит содержится в:
		
							родитель
							
								
									a57f082852
								
							
						
					
					
						коммит
						25b1915272
					
				
					 9 изменённых файлов: 648 добавлений и 611 удалений
				
			
		
							
								
								
									
										96
									
								
								feature.go
									
										
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										96
									
								
								feature.go
									
										
									
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,96 @@ | ||||||
|  | package godog | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cucumber/messages-go/v10" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type feature struct { | ||||||
|  | 	*messages.GherkinDocument | ||||||
|  | 	pickles []*messages.Pickle | ||||||
|  | 
 | ||||||
|  | 	time    time.Time | ||||||
|  | 	content []byte | ||||||
|  | 	order   int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sortFeaturesByName []*feature | ||||||
|  | 
 | ||||||
|  | func (s sortFeaturesByName) Len() int           { return len(s) } | ||||||
|  | func (s sortFeaturesByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name } | ||||||
|  | func (s sortFeaturesByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] } | ||||||
|  | 
 | ||||||
|  | type sortFeaturesByOrder []*feature | ||||||
|  | 
 | ||||||
|  | func (s sortFeaturesByOrder) Len() int           { return len(s) } | ||||||
|  | func (s sortFeaturesByOrder) Less(i, j int) bool { return s[i].order < s[j].order } | ||||||
|  | func (s sortFeaturesByOrder) Swap(i, j int)      { s[i], s[j] = s[j], s[i] } | ||||||
|  | 
 | ||||||
|  | func (f feature) findScenario(astScenarioID string) *messages.GherkinDocument_Feature_Scenario { | ||||||
|  | 	for _, child := range f.GherkinDocument.Feature.Children { | ||||||
|  | 		if sc := child.GetScenario(); sc != nil && sc.Id == astScenarioID { | ||||||
|  | 			return sc | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f feature) findBackground(astScenarioID string) *messages.GherkinDocument_Feature_Background { | ||||||
|  | 	var bg *messages.GherkinDocument_Feature_Background | ||||||
|  | 
 | ||||||
|  | 	for _, child := range f.GherkinDocument.Feature.Children { | ||||||
|  | 		if tmp := child.GetBackground(); tmp != nil { | ||||||
|  | 			bg = tmp | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if sc := child.GetScenario(); sc != nil && sc.Id == astScenarioID { | ||||||
|  | 			return bg | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f feature) findExample(exampleAstID string) (*messages.GherkinDocument_Feature_Scenario_Examples, *messages.GherkinDocument_Feature_TableRow) { | ||||||
|  | 	for _, child := range f.GherkinDocument.Feature.Children { | ||||||
|  | 		if sc := child.GetScenario(); sc != nil { | ||||||
|  | 			for _, example := range sc.Examples { | ||||||
|  | 				for _, row := range example.TableBody { | ||||||
|  | 					if row.Id == exampleAstID { | ||||||
|  | 						return example, row | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f feature) findStep(astStepID string) *messages.GherkinDocument_Feature_Step { | ||||||
|  | 	for _, child := range f.GherkinDocument.Feature.Children { | ||||||
|  | 		if sc := child.GetScenario(); sc != nil { | ||||||
|  | 			for _, step := range sc.GetSteps() { | ||||||
|  | 				if step.Id == astStepID { | ||||||
|  | 					return step | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if bg := child.GetBackground(); bg != nil { | ||||||
|  | 			for _, step := range bg.GetSteps() { | ||||||
|  | 				if step.Id == astStepID { | ||||||
|  | 					return step | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f feature) startedAt() time.Time { | ||||||
|  | 	return f.time | ||||||
|  | } | ||||||
							
								
								
									
										326
									
								
								fmt.go
									
										
									
									
									
								
							
							
						
						
									
										326
									
								
								fmt.go
									
										
									
									
									
								
							|  | @ -1,20 +1,12 @@ | ||||||
| package godog | package godog | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" |  | ||||||
| 	"sort" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"unicode/utf8" | ||||||
| 	"time" |  | ||||||
| 	"unicode" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/cucumber/messages-go/v10" | 	"github.com/cucumber/messages-go/v10" | ||||||
| 
 |  | ||||||
| 	"github.com/cucumber/godog/colors" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type registeredFormatter struct { | type registeredFormatter struct { | ||||||
|  | @ -99,301 +91,27 @@ type storageFormatter interface { | ||||||
| // suite name and io.Writer to record output | // suite name and io.Writer to record output | ||||||
| type FormatterFunc func(string, io.Writer) Formatter | type FormatterFunc func(string, io.Writer) Formatter | ||||||
| 
 | 
 | ||||||
| type stepResultStatus int |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	passed stepResultStatus = iota |  | ||||||
| 	failed |  | ||||||
| 	skipped |  | ||||||
| 	undefined |  | ||||||
| 	pending |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (st stepResultStatus) clr() colors.ColorFunc { |  | ||||||
| 	switch st { |  | ||||||
| 	case passed: |  | ||||||
| 		return green |  | ||||||
| 	case failed: |  | ||||||
| 		return red |  | ||||||
| 	case skipped: |  | ||||||
| 		return cyan |  | ||||||
| 	default: |  | ||||||
| 		return yellow |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (st stepResultStatus) String() string { |  | ||||||
| 	switch st { |  | ||||||
| 	case passed: |  | ||||||
| 		return "passed" |  | ||||||
| 	case failed: |  | ||||||
| 		return "failed" |  | ||||||
| 	case skipped: |  | ||||||
| 		return "skipped" |  | ||||||
| 	case undefined: |  | ||||||
| 		return "undefined" |  | ||||||
| 	case pending: |  | ||||||
| 		return "pending" |  | ||||||
| 	default: |  | ||||||
| 		return "unknown" |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type pickleStepResult struct { |  | ||||||
| 	Status     stepResultStatus |  | ||||||
| 	finishedAt time.Time |  | ||||||
| 	err        error |  | ||||||
| 
 |  | ||||||
| 	PickleID     string |  | ||||||
| 	PickleStepID string |  | ||||||
| 
 |  | ||||||
| 	def *StepDefinition |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func newStepResult(pickleID, pickleStepID string, match *StepDefinition) pickleStepResult { |  | ||||||
| 	return pickleStepResult{finishedAt: timeNowFunc(), PickleID: pickleID, PickleStepID: pickleStepID, def: match} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func newBaseFmt(suite string, out io.Writer) *basefmt { |  | ||||||
| 	return &basefmt{ |  | ||||||
| 		suiteName: suite, |  | ||||||
| 		startedAt: timeNowFunc(), |  | ||||||
| 		indent:    2, |  | ||||||
| 		out:       out, |  | ||||||
| 		lock:      new(sync.Mutex), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type basefmt struct { |  | ||||||
| 	suiteName string |  | ||||||
| 
 |  | ||||||
| 	out    io.Writer |  | ||||||
| 	owner  interface{} |  | ||||||
| 	indent int |  | ||||||
| 
 |  | ||||||
| 	storage *storage |  | ||||||
| 
 |  | ||||||
| 	startedAt time.Time |  | ||||||
| 
 |  | ||||||
| 	firstFeature *bool |  | ||||||
| 	lock         *sync.Mutex |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) setStorage(st *storage) { |  | ||||||
| 	f.storage = st |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) TestRunStarted() { |  | ||||||
| 	f.lock.Lock() |  | ||||||
| 	defer f.lock.Unlock() |  | ||||||
| 
 |  | ||||||
| 	firstFeature := true |  | ||||||
| 	f.firstFeature = &firstFeature |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) Pickle(p *messages.Pickle) {} |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {} |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) { |  | ||||||
| 	f.lock.Lock() |  | ||||||
| 	defer f.lock.Unlock() |  | ||||||
| 
 |  | ||||||
| 	*f.firstFeature = false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { |  | ||||||
| } |  | ||||||
| func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { |  | ||||||
| } |  | ||||||
| func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { |  | ||||||
| } |  | ||||||
| func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { |  | ||||||
| } |  | ||||||
| func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) Summary() { |  | ||||||
| 	var totalSc, passedSc, undefinedSc int |  | ||||||
| 	var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int |  | ||||||
| 
 |  | ||||||
| 	pickleResults := f.storage.mustGetPickleResults() |  | ||||||
| 	for _, pr := range pickleResults { |  | ||||||
| 		var prStatus stepResultStatus |  | ||||||
| 		totalSc++ |  | ||||||
| 
 |  | ||||||
| 		pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pr.PickleID) |  | ||||||
| 
 |  | ||||||
| 		if len(pickleStepResults) == 0 { |  | ||||||
| 			prStatus = undefined |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, sr := range pickleStepResults { |  | ||||||
| 			totalSt++ |  | ||||||
| 
 |  | ||||||
| 			switch sr.Status { |  | ||||||
| 			case passed: |  | ||||||
| 				prStatus = passed |  | ||||||
| 				passedSt++ |  | ||||||
| 			case failed: |  | ||||||
| 				prStatus = failed |  | ||||||
| 				failedSt++ |  | ||||||
| 			case skipped: |  | ||||||
| 				skippedSt++ |  | ||||||
| 			case undefined: |  | ||||||
| 				prStatus = undefined |  | ||||||
| 				undefinedSt++ |  | ||||||
| 			case pending: |  | ||||||
| 				prStatus = pending |  | ||||||
| 				pendingSt++ |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if prStatus == passed { |  | ||||||
| 			passedSc++ |  | ||||||
| 		} else if prStatus == undefined { |  | ||||||
| 			undefinedSc++ |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var steps, parts, scenarios []string |  | ||||||
| 	if passedSt > 0 { |  | ||||||
| 		steps = append(steps, green(fmt.Sprintf("%d passed", passedSt))) |  | ||||||
| 	} |  | ||||||
| 	if failedSt > 0 { |  | ||||||
| 		parts = append(parts, red(fmt.Sprintf("%d failed", failedSt))) |  | ||||||
| 		steps = append(steps, red(fmt.Sprintf("%d failed", failedSt))) |  | ||||||
| 	} |  | ||||||
| 	if pendingSt > 0 { |  | ||||||
| 		parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt))) |  | ||||||
| 		steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt))) |  | ||||||
| 	} |  | ||||||
| 	if undefinedSt > 0 { |  | ||||||
| 		parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc))) |  | ||||||
| 		steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt))) |  | ||||||
| 	} else if undefinedSc > 0 { |  | ||||||
| 		// there may be some scenarios without steps |  | ||||||
| 		parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc))) |  | ||||||
| 	} |  | ||||||
| 	if skippedSt > 0 { |  | ||||||
| 		steps = append(steps, cyan(fmt.Sprintf("%d skipped", skippedSt))) |  | ||||||
| 	} |  | ||||||
| 	if passedSc > 0 { |  | ||||||
| 		scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc))) |  | ||||||
| 	} |  | ||||||
| 	scenarios = append(scenarios, parts...) |  | ||||||
| 	elapsed := timeNowFunc().Sub(f.startedAt) |  | ||||||
| 
 |  | ||||||
| 	fmt.Fprintln(f.out, "") |  | ||||||
| 
 |  | ||||||
| 	if totalSc == 0 { |  | ||||||
| 		fmt.Fprintln(f.out, "No scenarios") |  | ||||||
| 	} else { |  | ||||||
| 		fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", totalSc, strings.Join(scenarios, ", "))) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if totalSt == 0 { |  | ||||||
| 		fmt.Fprintln(f.out, "No steps") |  | ||||||
| 	} else { |  | ||||||
| 		fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", totalSt, strings.Join(steps, ", "))) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	elapsedString := elapsed.String() |  | ||||||
| 	if elapsed.Nanoseconds() == 0 { |  | ||||||
| 		// go 1.5 and 1.6 prints 0 instead of 0s, if duration is zero. |  | ||||||
| 		elapsedString = "0s" |  | ||||||
| 	} |  | ||||||
| 	fmt.Fprintln(f.out, elapsedString) |  | ||||||
| 
 |  | ||||||
| 	// prints used randomization seed |  | ||||||
| 	seed, err := strconv.ParseInt(os.Getenv("GODOG_SEED"), 10, 64) |  | ||||||
| 	if err == nil && seed != 0 { |  | ||||||
| 		fmt.Fprintln(f.out, "") |  | ||||||
| 		fmt.Fprintln(f.out, "Randomized with seed:", colors.Yellow(seed)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if text := f.snippets(); text != "" { |  | ||||||
| 		fmt.Fprintln(f.out, "") |  | ||||||
| 		fmt.Fprintln(f.out, yellow("You can implement step definitions for undefined steps with these snippets:")) |  | ||||||
| 		fmt.Fprintln(f.out, yellow(text)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) Sync(cf ConcurrentFormatter) { |  | ||||||
| 	if source, ok := cf.(*basefmt); ok { |  | ||||||
| 		f.lock = source.lock |  | ||||||
| 		f.firstFeature = source.firstFeature |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) Copy(cf ConcurrentFormatter) {} |  | ||||||
| 
 |  | ||||||
| func (f *basefmt) snippets() string { |  | ||||||
| 	undefinedStepResults := f.storage.mustGetPickleStepResultsByStatus(undefined) |  | ||||||
| 	if len(undefinedStepResults) == 0 { |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var index int |  | ||||||
| 	var snips []undefinedSnippet |  | ||||||
| 	// build snippets |  | ||||||
| 	for _, u := range undefinedStepResults { |  | ||||||
| 		pickleStep := f.storage.mustGetPickleStep(u.PickleStepID) |  | ||||||
| 
 |  | ||||||
| 		steps := []string{pickleStep.Text} |  | ||||||
| 		arg := pickleStep.Argument |  | ||||||
| 		if u.def != nil { |  | ||||||
| 			steps = u.def.undefined |  | ||||||
| 			arg = nil |  | ||||||
| 		} |  | ||||||
| 		for _, step := range steps { |  | ||||||
| 			expr := snippetExprCleanup.ReplaceAllString(step, "\\$1") |  | ||||||
| 			expr = snippetNumbers.ReplaceAllString(expr, "(\\d+)") |  | ||||||
| 			expr = snippetExprQuoted.ReplaceAllString(expr, "$1\"([^\"]*)\"$2") |  | ||||||
| 			expr = "^" + strings.TrimSpace(expr) + "$" |  | ||||||
| 
 |  | ||||||
| 			name := snippetNumbers.ReplaceAllString(step, " ") |  | ||||||
| 			name = snippetExprQuoted.ReplaceAllString(name, " ") |  | ||||||
| 			name = strings.TrimSpace(snippetMethodName.ReplaceAllString(name, "")) |  | ||||||
| 			var words []string |  | ||||||
| 			for i, w := range strings.Split(name, " ") { |  | ||||||
| 				switch { |  | ||||||
| 				case i != 0: |  | ||||||
| 					w = strings.Title(w) |  | ||||||
| 				case len(w) > 0: |  | ||||||
| 					w = string(unicode.ToLower(rune(w[0]))) + w[1:] |  | ||||||
| 				} |  | ||||||
| 				words = append(words, w) |  | ||||||
| 			} |  | ||||||
| 			name = strings.Join(words, "") |  | ||||||
| 			if len(name) == 0 { |  | ||||||
| 				index++ |  | ||||||
| 				name = fmt.Sprintf("StepDefinitioninition%d", index) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			var found bool |  | ||||||
| 			for _, snip := range snips { |  | ||||||
| 				if snip.Expr == expr { |  | ||||||
| 					found = true |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			if !found { |  | ||||||
| 				snips = append(snips, undefinedSnippet{Method: name, Expr: expr, argument: arg}) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	sort.Sort(snippetSortByMethod(snips)) |  | ||||||
| 
 |  | ||||||
| 	var buf bytes.Buffer |  | ||||||
| 	if err := undefinedSnippetsTpl.Execute(&buf, snips); err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	// there may be trailing spaces |  | ||||||
| 	return strings.Replace(buf.String(), " \n", "\n", -1) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool { | func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool { | ||||||
| 	return pickle.Steps[len(pickle.Steps)-1].Id == step.Id | 	return pickle.Steps[len(pickle.Steps)-1].Id == step.Id | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func printStepDefinitions(steps []*StepDefinition, w io.Writer) { | ||||||
|  | 	var longest int | ||||||
|  | 	for _, def := range steps { | ||||||
|  | 		n := utf8.RuneCountInString(def.Expr.String()) | ||||||
|  | 		if longest < n { | ||||||
|  | 			longest = n | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, def := range steps { | ||||||
|  | 		n := utf8.RuneCountInString(def.Expr.String()) | ||||||
|  | 		location := def.definitionID() | ||||||
|  | 		spaces := strings.Repeat(" ", longest-n) | ||||||
|  | 		fmt.Fprintln(w, yellow(def.Expr.String())+spaces, blackb("# "+location)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(steps) == 0 { | ||||||
|  | 		fmt.Fprintln(w, "there were no contexts registered, could not find any step definition..") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										258
									
								
								fmt_base.go
									
										
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										258
									
								
								fmt_base.go
									
										
									
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,258 @@ | ||||||
|  | package godog | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"sort" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 	"unicode" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cucumber/messages-go/v10" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cucumber/godog/colors" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func newBaseFmt(suite string, out io.Writer) *basefmt { | ||||||
|  | 	return &basefmt{ | ||||||
|  | 		suiteName: suite, | ||||||
|  | 		startedAt: timeNowFunc(), | ||||||
|  | 		indent:    2, | ||||||
|  | 		out:       out, | ||||||
|  | 		lock:      new(sync.Mutex), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type basefmt struct { | ||||||
|  | 	suiteName string | ||||||
|  | 
 | ||||||
|  | 	out    io.Writer | ||||||
|  | 	owner  interface{} | ||||||
|  | 	indent int | ||||||
|  | 
 | ||||||
|  | 	storage *storage | ||||||
|  | 
 | ||||||
|  | 	startedAt time.Time | ||||||
|  | 
 | ||||||
|  | 	firstFeature *bool | ||||||
|  | 	lock         *sync.Mutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) setStorage(st *storage) { | ||||||
|  | 	f.storage = st | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) TestRunStarted() { | ||||||
|  | 	f.lock.Lock() | ||||||
|  | 	defer f.lock.Unlock() | ||||||
|  | 
 | ||||||
|  | 	firstFeature := true | ||||||
|  | 	f.firstFeature = &firstFeature | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) Pickle(p *messages.Pickle) {} | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {} | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) { | ||||||
|  | 	f.lock.Lock() | ||||||
|  | 	defer f.lock.Unlock() | ||||||
|  | 
 | ||||||
|  | 	*f.firstFeature = false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { | ||||||
|  | } | ||||||
|  | func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { | ||||||
|  | } | ||||||
|  | func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { | ||||||
|  | } | ||||||
|  | func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { | ||||||
|  | } | ||||||
|  | func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) Summary() { | ||||||
|  | 	var totalSc, passedSc, undefinedSc int | ||||||
|  | 	var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int | ||||||
|  | 
 | ||||||
|  | 	pickleResults := f.storage.mustGetPickleResults() | ||||||
|  | 	for _, pr := range pickleResults { | ||||||
|  | 		var prStatus stepResultStatus | ||||||
|  | 		totalSc++ | ||||||
|  | 
 | ||||||
|  | 		pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pr.PickleID) | ||||||
|  | 
 | ||||||
|  | 		if len(pickleStepResults) == 0 { | ||||||
|  | 			prStatus = undefined | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, sr := range pickleStepResults { | ||||||
|  | 			totalSt++ | ||||||
|  | 
 | ||||||
|  | 			switch sr.Status { | ||||||
|  | 			case passed: | ||||||
|  | 				prStatus = passed | ||||||
|  | 				passedSt++ | ||||||
|  | 			case failed: | ||||||
|  | 				prStatus = failed | ||||||
|  | 				failedSt++ | ||||||
|  | 			case skipped: | ||||||
|  | 				skippedSt++ | ||||||
|  | 			case undefined: | ||||||
|  | 				prStatus = undefined | ||||||
|  | 				undefinedSt++ | ||||||
|  | 			case pending: | ||||||
|  | 				prStatus = pending | ||||||
|  | 				pendingSt++ | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if prStatus == passed { | ||||||
|  | 			passedSc++ | ||||||
|  | 		} else if prStatus == undefined { | ||||||
|  | 			undefinedSc++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var steps, parts, scenarios []string | ||||||
|  | 	if passedSt > 0 { | ||||||
|  | 		steps = append(steps, green(fmt.Sprintf("%d passed", passedSt))) | ||||||
|  | 	} | ||||||
|  | 	if failedSt > 0 { | ||||||
|  | 		parts = append(parts, red(fmt.Sprintf("%d failed", failedSt))) | ||||||
|  | 		steps = append(steps, red(fmt.Sprintf("%d failed", failedSt))) | ||||||
|  | 	} | ||||||
|  | 	if pendingSt > 0 { | ||||||
|  | 		parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt))) | ||||||
|  | 		steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt))) | ||||||
|  | 	} | ||||||
|  | 	if undefinedSt > 0 { | ||||||
|  | 		parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc))) | ||||||
|  | 		steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt))) | ||||||
|  | 	} else if undefinedSc > 0 { | ||||||
|  | 		// there may be some scenarios without steps | ||||||
|  | 		parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc))) | ||||||
|  | 	} | ||||||
|  | 	if skippedSt > 0 { | ||||||
|  | 		steps = append(steps, cyan(fmt.Sprintf("%d skipped", skippedSt))) | ||||||
|  | 	} | ||||||
|  | 	if passedSc > 0 { | ||||||
|  | 		scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc))) | ||||||
|  | 	} | ||||||
|  | 	scenarios = append(scenarios, parts...) | ||||||
|  | 	elapsed := timeNowFunc().Sub(f.startedAt) | ||||||
|  | 
 | ||||||
|  | 	fmt.Fprintln(f.out, "") | ||||||
|  | 
 | ||||||
|  | 	if totalSc == 0 { | ||||||
|  | 		fmt.Fprintln(f.out, "No scenarios") | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", totalSc, strings.Join(scenarios, ", "))) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if totalSt == 0 { | ||||||
|  | 		fmt.Fprintln(f.out, "No steps") | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", totalSt, strings.Join(steps, ", "))) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	elapsedString := elapsed.String() | ||||||
|  | 	if elapsed.Nanoseconds() == 0 { | ||||||
|  | 		// go 1.5 and 1.6 prints 0 instead of 0s, if duration is zero. | ||||||
|  | 		elapsedString = "0s" | ||||||
|  | 	} | ||||||
|  | 	fmt.Fprintln(f.out, elapsedString) | ||||||
|  | 
 | ||||||
|  | 	// prints used randomization seed | ||||||
|  | 	seed, err := strconv.ParseInt(os.Getenv("GODOG_SEED"), 10, 64) | ||||||
|  | 	if err == nil && seed != 0 { | ||||||
|  | 		fmt.Fprintln(f.out, "") | ||||||
|  | 		fmt.Fprintln(f.out, "Randomized with seed:", colors.Yellow(seed)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if text := f.snippets(); text != "" { | ||||||
|  | 		fmt.Fprintln(f.out, "") | ||||||
|  | 		fmt.Fprintln(f.out, yellow("You can implement step definitions for undefined steps with these snippets:")) | ||||||
|  | 		fmt.Fprintln(f.out, yellow(text)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) Sync(cf ConcurrentFormatter) { | ||||||
|  | 	if source, ok := cf.(*basefmt); ok { | ||||||
|  | 		f.lock = source.lock | ||||||
|  | 		f.firstFeature = source.firstFeature | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) Copy(cf ConcurrentFormatter) {} | ||||||
|  | 
 | ||||||
|  | func (f *basefmt) snippets() string { | ||||||
|  | 	undefinedStepResults := f.storage.mustGetPickleStepResultsByStatus(undefined) | ||||||
|  | 	if len(undefinedStepResults) == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var index int | ||||||
|  | 	var snips []undefinedSnippet | ||||||
|  | 	// build snippets | ||||||
|  | 	for _, u := range undefinedStepResults { | ||||||
|  | 		pickleStep := f.storage.mustGetPickleStep(u.PickleStepID) | ||||||
|  | 
 | ||||||
|  | 		steps := []string{pickleStep.Text} | ||||||
|  | 		arg := pickleStep.Argument | ||||||
|  | 		if u.def != nil { | ||||||
|  | 			steps = u.def.undefined | ||||||
|  | 			arg = nil | ||||||
|  | 		} | ||||||
|  | 		for _, step := range steps { | ||||||
|  | 			expr := snippetExprCleanup.ReplaceAllString(step, "\\$1") | ||||||
|  | 			expr = snippetNumbers.ReplaceAllString(expr, "(\\d+)") | ||||||
|  | 			expr = snippetExprQuoted.ReplaceAllString(expr, "$1\"([^\"]*)\"$2") | ||||||
|  | 			expr = "^" + strings.TrimSpace(expr) + "$" | ||||||
|  | 
 | ||||||
|  | 			name := snippetNumbers.ReplaceAllString(step, " ") | ||||||
|  | 			name = snippetExprQuoted.ReplaceAllString(name, " ") | ||||||
|  | 			name = strings.TrimSpace(snippetMethodName.ReplaceAllString(name, "")) | ||||||
|  | 			var words []string | ||||||
|  | 			for i, w := range strings.Split(name, " ") { | ||||||
|  | 				switch { | ||||||
|  | 				case i != 0: | ||||||
|  | 					w = strings.Title(w) | ||||||
|  | 				case len(w) > 0: | ||||||
|  | 					w = string(unicode.ToLower(rune(w[0]))) + w[1:] | ||||||
|  | 				} | ||||||
|  | 				words = append(words, w) | ||||||
|  | 			} | ||||||
|  | 			name = strings.Join(words, "") | ||||||
|  | 			if len(name) == 0 { | ||||||
|  | 				index++ | ||||||
|  | 				name = fmt.Sprintf("StepDefinitioninition%d", index) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			var found bool | ||||||
|  | 			for _, snip := range snips { | ||||||
|  | 				if snip.Expr == expr { | ||||||
|  | 					found = true | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if !found { | ||||||
|  | 				snips = append(snips, undefinedSnippet{Method: name, Expr: expr, argument: arg}) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sort.Sort(snippetSortByMethod(snips)) | ||||||
|  | 
 | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	if err := undefinedSnippetsTpl.Execute(&buf, snips); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	// there may be trailing spaces | ||||||
|  | 	return strings.Replace(buf.String(), " \n", "\n", -1) | ||||||
|  | } | ||||||
							
								
								
									
										169
									
								
								parser.go
									
										
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										169
									
								
								parser.go
									
										
									
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,169 @@ | ||||||
|  | package godog | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"sort" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cucumber/gherkin-go/v11" | ||||||
|  | 	"github.com/cucumber/messages-go/v10" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var pathLineRe = regexp.MustCompile(`:([\d]+)$`) | ||||||
|  | 
 | ||||||
|  | func extractFeaturePathLine(p string) (string, int) { | ||||||
|  | 	line := -1 | ||||||
|  | 	retPath := p | ||||||
|  | 	if m := pathLineRe.FindStringSubmatch(p); len(m) > 0 { | ||||||
|  | 		if i, err := strconv.Atoi(m[1]); err == nil { | ||||||
|  | 			line = i | ||||||
|  | 			retPath = p[:strings.LastIndexByte(p, ':')] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return retPath, line | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) { | ||||||
|  | 	reader, err := os.Open(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer reader.Close() | ||||||
|  | 
 | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("%s - %v", path, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	gherkinDocument.Uri = path | ||||||
|  | 	pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc) | ||||||
|  | 
 | ||||||
|  | 	f := feature{GherkinDocument: gherkinDocument, pickles: pickles, content: buf.Bytes()} | ||||||
|  | 	return &f, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) { | ||||||
|  | 	var features []*feature | ||||||
|  | 	return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error { | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if f.IsDir() { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if !strings.HasSuffix(p, ".feature") { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		feat, err := parseFeatureFile(p, newIDFunc) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		features = append(features, feat) | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parsePath(path string) ([]*feature, error) { | ||||||
|  | 	var features []*feature | ||||||
|  | 
 | ||||||
|  | 	path, line := extractFeaturePathLine(path) | ||||||
|  | 
 | ||||||
|  | 	fi, err := os.Stat(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return features, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	newIDFunc := (&messages.Incrementing{}).NewId | ||||||
|  | 
 | ||||||
|  | 	if fi.IsDir() { | ||||||
|  | 		return parseFeatureDir(path, newIDFunc) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ft, err := parseFeatureFile(path, newIDFunc) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return features, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// filter scenario by line number | ||||||
|  | 	var pickles []*messages.Pickle | ||||||
|  | 	for _, pickle := range ft.pickles { | ||||||
|  | 		sc := ft.findScenario(pickle.AstNodeIds[0]) | ||||||
|  | 
 | ||||||
|  | 		if line == -1 || uint32(line) == sc.Location.Line { | ||||||
|  | 			pickles = append(pickles, pickle) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	ft.pickles = pickles | ||||||
|  | 
 | ||||||
|  | 	return append(features, ft), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func parseFeatures(filter string, paths []string) ([]*feature, error) { | ||||||
|  | 	var order int | ||||||
|  | 
 | ||||||
|  | 	uniqueFeatureURI := make(map[string]*feature) | ||||||
|  | 	for _, path := range paths { | ||||||
|  | 		feats, err := parsePath(path) | ||||||
|  | 
 | ||||||
|  | 		switch { | ||||||
|  | 		case os.IsNotExist(err): | ||||||
|  | 			return nil, fmt.Errorf(`feature path "%s" is not available`, path) | ||||||
|  | 		case os.IsPermission(err): | ||||||
|  | 			return nil, fmt.Errorf(`feature path "%s" is not accessible`, path) | ||||||
|  | 		case err != nil: | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, ft := range feats { | ||||||
|  | 			if _, duplicate := uniqueFeatureURI[ft.Uri]; duplicate { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ft.order = order | ||||||
|  | 			order++ | ||||||
|  | 			uniqueFeatureURI[ft.Uri] = ft | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return filterFeatures(filter, uniqueFeatureURI), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func filterFeatures(tags string, collected map[string]*feature) (features []*feature) { | ||||||
|  | 	for _, ft := range collected { | ||||||
|  | 		ft.pickles = applyTagFilter(tags, ft.pickles) | ||||||
|  | 
 | ||||||
|  | 		if ft.Feature != nil { | ||||||
|  | 			features = append(features, ft) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sort.Sort(sortFeaturesByOrder(features)) | ||||||
|  | 
 | ||||||
|  | 	return features | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func applyTagFilter(tags string, pickles []*messages.Pickle) (result []*messages.Pickle) { | ||||||
|  | 	if len(tags) == 0 { | ||||||
|  | 		return pickles | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, pickle := range pickles { | ||||||
|  | 		if matchesTags(tags, pickle.Tags) { | ||||||
|  | 			result = append(result, pickle) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								results.go
									
										
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										77
									
								
								results.go
									
										
									
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,77 @@ | ||||||
|  | package godog | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cucumber/godog/colors" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type pickleResult struct { | ||||||
|  | 	PickleID  string | ||||||
|  | 	StartedAt time.Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type pickleStepResult struct { | ||||||
|  | 	Status     stepResultStatus | ||||||
|  | 	finishedAt time.Time | ||||||
|  | 	err        error | ||||||
|  | 
 | ||||||
|  | 	PickleID     string | ||||||
|  | 	PickleStepID string | ||||||
|  | 
 | ||||||
|  | 	def *StepDefinition | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newStepResult(pickleID, pickleStepID string, match *StepDefinition) pickleStepResult { | ||||||
|  | 	return pickleStepResult{finishedAt: timeNowFunc(), PickleID: pickleID, PickleStepID: pickleStepID, def: match} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sortPickleStepResultsByPickleStepID []pickleStepResult | ||||||
|  | 
 | ||||||
|  | func (s sortPickleStepResultsByPickleStepID) Len() int { return len(s) } | ||||||
|  | func (s sortPickleStepResultsByPickleStepID) Less(i, j int) bool { | ||||||
|  | 	iID := mustConvertStringToInt(s[i].PickleStepID) | ||||||
|  | 	jID := mustConvertStringToInt(s[j].PickleStepID) | ||||||
|  | 	return iID < jID | ||||||
|  | } | ||||||
|  | func (s sortPickleStepResultsByPickleStepID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | ||||||
|  | 
 | ||||||
|  | type stepResultStatus int | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	passed stepResultStatus = iota | ||||||
|  | 	failed | ||||||
|  | 	skipped | ||||||
|  | 	undefined | ||||||
|  | 	pending | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (st stepResultStatus) clr() colors.ColorFunc { | ||||||
|  | 	switch st { | ||||||
|  | 	case passed: | ||||||
|  | 		return green | ||||||
|  | 	case failed: | ||||||
|  | 		return red | ||||||
|  | 	case skipped: | ||||||
|  | 		return cyan | ||||||
|  | 	default: | ||||||
|  | 		return yellow | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (st stepResultStatus) String() string { | ||||||
|  | 	switch st { | ||||||
|  | 	case passed: | ||||||
|  | 		return "passed" | ||||||
|  | 	case failed: | ||||||
|  | 		return "failed" | ||||||
|  | 	case skipped: | ||||||
|  | 		return "skipped" | ||||||
|  | 	case undefined: | ||||||
|  | 		return "undefined" | ||||||
|  | 	case pending: | ||||||
|  | 		return "pending" | ||||||
|  | 	default: | ||||||
|  | 		return "unknown" | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								run.go
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								run.go
									
										
									
									
									
								
							|  | @ -199,7 +199,7 @@ func runWithOptions(suite string, runner runner, opt Options) int { | ||||||
| 	if opt.ShowStepDefinitions { | 	if opt.ShowStepDefinitions { | ||||||
| 		s := &Suite{} | 		s := &Suite{} | ||||||
| 		runner.initializer(s) | 		runner.initializer(s) | ||||||
| 		s.printStepDefinitions(output) | 		printStepDefinitions(s.steps, output) | ||||||
| 		return exitOptionError | 		return exitOptionError | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,7 +34,8 @@ func TestPrintsStepDefinitions(t *testing.T) { | ||||||
| 	for _, step := range steps { | 	for _, step := range steps { | ||||||
| 		s.Step(step, okStep) | 		s.Step(step, okStep) | ||||||
| 	} | 	} | ||||||
| 	s.printStepDefinitions(w) | 
 | ||||||
|  | 	printStepDefinitions(s.steps, w) | ||||||
| 
 | 
 | ||||||
| 	out := buf.String() | 	out := buf.String() | ||||||
| 	ref := `okStep` | 	ref := `okStep` | ||||||
|  | @ -52,7 +53,8 @@ func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) { | ||||||
| 	var buf bytes.Buffer | 	var buf bytes.Buffer | ||||||
| 	w := colors.Uncolored(&buf) | 	w := colors.Uncolored(&buf) | ||||||
| 	s := &Suite{} | 	s := &Suite{} | ||||||
| 	s.printStepDefinitions(w) | 
 | ||||||
|  | 	printStepDefinitions(s.steps, w) | ||||||
| 
 | 
 | ||||||
| 	out := strings.TrimSpace(buf.String()) | 	out := strings.TrimSpace(buf.String()) | ||||||
| 	assert.Equal(t, "there were no contexts registered, could not find any step definition..", out) | 	assert.Equal(t, "there were no contexts registered, could not find any step definition..", out) | ||||||
|  |  | ||||||
							
								
								
									
										304
									
								
								suite.go
									
										
									
									
									
								
							
							
						
						
									
										304
									
								
								suite.go
									
										
									
									
									
								
							|  | @ -1,143 +1,17 @@ | ||||||
| package godog | package godog | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"sort" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" |  | ||||||
| 	"unicode/utf8" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/cucumber/gherkin-go/v11" |  | ||||||
| 	"github.com/cucumber/messages-go/v10" | 	"github.com/cucumber/messages-go/v10" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var errorInterface = reflect.TypeOf((*error)(nil)).Elem() | var errorInterface = reflect.TypeOf((*error)(nil)).Elem() | ||||||
| var typeOfBytes = reflect.TypeOf([]byte(nil)) | var typeOfBytes = reflect.TypeOf([]byte(nil)) | ||||||
| 
 | 
 | ||||||
| type feature struct { |  | ||||||
| 	*messages.GherkinDocument |  | ||||||
| 	pickles []*messages.Pickle |  | ||||||
| 
 |  | ||||||
| 	time    time.Time |  | ||||||
| 	content []byte |  | ||||||
| 	order   int |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f feature) findScenario(astScenarioID string) *messages.GherkinDocument_Feature_Scenario { |  | ||||||
| 	for _, child := range f.GherkinDocument.Feature.Children { |  | ||||||
| 		if sc := child.GetScenario(); sc != nil && sc.Id == astScenarioID { |  | ||||||
| 			return sc |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f feature) findBackground(astScenarioID string) *messages.GherkinDocument_Feature_Background { |  | ||||||
| 	var bg *messages.GherkinDocument_Feature_Background |  | ||||||
| 
 |  | ||||||
| 	for _, child := range f.GherkinDocument.Feature.Children { |  | ||||||
| 		if tmp := child.GetBackground(); tmp != nil { |  | ||||||
| 			bg = tmp |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if sc := child.GetScenario(); sc != nil && sc.Id == astScenarioID { |  | ||||||
| 			return bg |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f feature) findExample(exampleAstID string) (*messages.GherkinDocument_Feature_Scenario_Examples, *messages.GherkinDocument_Feature_TableRow) { |  | ||||||
| 	for _, child := range f.GherkinDocument.Feature.Children { |  | ||||||
| 		if sc := child.GetScenario(); sc != nil { |  | ||||||
| 			for _, example := range sc.Examples { |  | ||||||
| 				for _, row := range example.TableBody { |  | ||||||
| 					if row.Id == exampleAstID { |  | ||||||
| 						return example, row |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f feature) findStep(astStepID string) *messages.GherkinDocument_Feature_Step { |  | ||||||
| 	for _, child := range f.GherkinDocument.Feature.Children { |  | ||||||
| 		if sc := child.GetScenario(); sc != nil { |  | ||||||
| 			for _, step := range sc.GetSteps() { |  | ||||||
| 				if step.Id == astStepID { |  | ||||||
| 					return step |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if bg := child.GetBackground(); bg != nil { |  | ||||||
| 			for _, step := range bg.GetSteps() { |  | ||||||
| 				if step.Id == astStepID { |  | ||||||
| 					return step |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (f feature) startedAt() time.Time { |  | ||||||
| 	return f.time |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type sortFeaturesByName []*feature |  | ||||||
| 
 |  | ||||||
| func (s sortFeaturesByName) Len() int           { return len(s) } |  | ||||||
| func (s sortFeaturesByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name } |  | ||||||
| func (s sortFeaturesByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] } |  | ||||||
| 
 |  | ||||||
| type sortPicklesByID []*messages.Pickle |  | ||||||
| 
 |  | ||||||
| func (s sortPicklesByID) Len() int { return len(s) } |  | ||||||
| func (s sortPicklesByID) Less(i, j int) bool { |  | ||||||
| 	iID := mustConvertStringToInt(s[i].Id) |  | ||||||
| 	jID := mustConvertStringToInt(s[j].Id) |  | ||||||
| 	return iID < jID |  | ||||||
| } |  | ||||||
| func (s sortPicklesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |  | ||||||
| 
 |  | ||||||
| type sortPickleStepResultsByPickleStepID []pickleStepResult |  | ||||||
| 
 |  | ||||||
| func (s sortPickleStepResultsByPickleStepID) Len() int { return len(s) } |  | ||||||
| func (s sortPickleStepResultsByPickleStepID) Less(i, j int) bool { |  | ||||||
| 	iID := mustConvertStringToInt(s[i].PickleStepID) |  | ||||||
| 	jID := mustConvertStringToInt(s[j].PickleStepID) |  | ||||||
| 	return iID < jID |  | ||||||
| } |  | ||||||
| func (s sortPickleStepResultsByPickleStepID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |  | ||||||
| 
 |  | ||||||
| func mustConvertStringToInt(s string) int { |  | ||||||
| 	i, err := strconv.Atoi(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return i |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type pickleResult struct { |  | ||||||
| 	PickleID  string |  | ||||||
| 	StartedAt time.Time |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ErrUndefined is returned in case if step definition was not found | // ErrUndefined is returned in case if step definition was not found | ||||||
| var ErrUndefined = fmt.Errorf("step is undefined") | var ErrUndefined = fmt.Errorf("step is undefined") | ||||||
| 
 | 
 | ||||||
|  | @ -654,181 +528,3 @@ func (s *Suite) runPickle(pickle *messages.Pickle) (err error) { | ||||||
| 
 | 
 | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func (s *Suite) printStepDefinitions(w io.Writer) { |  | ||||||
| 	var longest int |  | ||||||
| 	for _, def := range s.steps { |  | ||||||
| 		n := utf8.RuneCountInString(def.Expr.String()) |  | ||||||
| 		if longest < n { |  | ||||||
| 			longest = n |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	for _, def := range s.steps { |  | ||||||
| 		n := utf8.RuneCountInString(def.Expr.String()) |  | ||||||
| 		location := def.definitionID() |  | ||||||
| 		spaces := strings.Repeat(" ", longest-n) |  | ||||||
| 		fmt.Fprintln(w, yellow(def.Expr.String())+spaces, blackb("# "+location)) |  | ||||||
| 	} |  | ||||||
| 	if len(s.steps) == 0 { |  | ||||||
| 		fmt.Fprintln(w, "there were no contexts registered, could not find any step definition..") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var pathLineRe = regexp.MustCompile(`:([\d]+)$`) |  | ||||||
| 
 |  | ||||||
| func extractFeaturePathLine(p string) (string, int) { |  | ||||||
| 	line := -1 |  | ||||||
| 	retPath := p |  | ||||||
| 	if m := pathLineRe.FindStringSubmatch(p); len(m) > 0 { |  | ||||||
| 		if i, err := strconv.Atoi(m[1]); err == nil { |  | ||||||
| 			line = i |  | ||||||
| 			retPath = p[:strings.LastIndexByte(p, ':')] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return retPath, line |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) { |  | ||||||
| 	reader, err := os.Open(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	defer reader.Close() |  | ||||||
| 
 |  | ||||||
| 	var buf bytes.Buffer |  | ||||||
| 	gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("%s - %v", path, err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	gherkinDocument.Uri = path |  | ||||||
| 	pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc) |  | ||||||
| 
 |  | ||||||
| 	f := feature{GherkinDocument: gherkinDocument, pickles: pickles, content: buf.Bytes()} |  | ||||||
| 	return &f, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) { |  | ||||||
| 	var features []*feature |  | ||||||
| 	return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error { |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if f.IsDir() { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if !strings.HasSuffix(p, ".feature") { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		feat, err := parseFeatureFile(p, newIDFunc) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		features = append(features, feat) |  | ||||||
| 		return nil |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parsePath(path string) ([]*feature, error) { |  | ||||||
| 	var features []*feature |  | ||||||
| 
 |  | ||||||
| 	path, line := extractFeaturePathLine(path) |  | ||||||
| 
 |  | ||||||
| 	fi, err := os.Stat(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return features, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	newIDFunc := (&messages.Incrementing{}).NewId |  | ||||||
| 
 |  | ||||||
| 	if fi.IsDir() { |  | ||||||
| 		return parseFeatureDir(path, newIDFunc) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ft, err := parseFeatureFile(path, newIDFunc) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return features, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// filter scenario by line number |  | ||||||
| 	var pickles []*messages.Pickle |  | ||||||
| 	for _, pickle := range ft.pickles { |  | ||||||
| 		sc := ft.findScenario(pickle.AstNodeIds[0]) |  | ||||||
| 
 |  | ||||||
| 		if line == -1 || uint32(line) == sc.Location.Line { |  | ||||||
| 			pickles = append(pickles, pickle) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	ft.pickles = pickles |  | ||||||
| 
 |  | ||||||
| 	return append(features, ft), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseFeatures(filter string, paths []string) ([]*feature, error) { |  | ||||||
| 	var order int |  | ||||||
| 
 |  | ||||||
| 	uniqueFeatureURI := make(map[string]*feature) |  | ||||||
| 	for _, path := range paths { |  | ||||||
| 		feats, err := parsePath(path) |  | ||||||
| 
 |  | ||||||
| 		switch { |  | ||||||
| 		case os.IsNotExist(err): |  | ||||||
| 			return nil, fmt.Errorf(`feature path "%s" is not available`, path) |  | ||||||
| 		case os.IsPermission(err): |  | ||||||
| 			return nil, fmt.Errorf(`feature path "%s" is not accessible`, path) |  | ||||||
| 		case err != nil: |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, ft := range feats { |  | ||||||
| 			if _, duplicate := uniqueFeatureURI[ft.Uri]; duplicate { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			ft.order = order |  | ||||||
| 			order++ |  | ||||||
| 			uniqueFeatureURI[ft.Uri] = ft |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return filterFeatures(filter, uniqueFeatureURI), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type sortFeaturesByOrder []*feature |  | ||||||
| 
 |  | ||||||
| func (s sortFeaturesByOrder) Len() int           { return len(s) } |  | ||||||
| func (s sortFeaturesByOrder) Less(i, j int) bool { return s[i].order < s[j].order } |  | ||||||
| func (s sortFeaturesByOrder) Swap(i, j int)      { s[i], s[j] = s[j], s[i] } |  | ||||||
| 
 |  | ||||||
| func filterFeatures(tags string, collected map[string]*feature) (features []*feature) { |  | ||||||
| 	for _, ft := range collected { |  | ||||||
| 		ft.pickles = applyTagFilter(tags, ft.pickles) |  | ||||||
| 
 |  | ||||||
| 		if ft.Feature != nil { |  | ||||||
| 			features = append(features, ft) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	sort.Sort(sortFeaturesByOrder(features)) |  | ||||||
| 
 |  | ||||||
| 	return features |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func applyTagFilter(tags string, pickles []*messages.Pickle) (result []*messages.Pickle) { |  | ||||||
| 	if len(tags) == 0 { |  | ||||||
| 		return pickles |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, pickle := range pickles { |  | ||||||
| 		if matchesTags(tags, pickle.Tags) { |  | ||||||
| 			result = append(result, pickle) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								utils.go
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								utils.go
									
										
									
									
									
								
							|  | @ -1,10 +1,12 @@ | ||||||
| package godog | package godog | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/cucumber/godog/colors" | 	"github.com/cucumber/godog/colors" | ||||||
|  | 	"github.com/cucumber/messages-go/v10" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -37,3 +39,22 @@ func trimAllLines(s string) string { | ||||||
| 	} | 	} | ||||||
| 	return strings.Join(lines, "\n") | 	return strings.Join(lines, "\n") | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type sortPicklesByID []*messages.Pickle | ||||||
|  | 
 | ||||||
|  | func (s sortPicklesByID) Len() int { return len(s) } | ||||||
|  | func (s sortPicklesByID) Less(i, j int) bool { | ||||||
|  | 	iID := mustConvertStringToInt(s[i].Id) | ||||||
|  | 	jID := mustConvertStringToInt(s[j].Id) | ||||||
|  | 	return iID < jID | ||||||
|  | } | ||||||
|  | func (s sortPicklesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | ||||||
|  | 
 | ||||||
|  | func mustConvertStringToInt(s string) int { | ||||||
|  | 	i, err := strconv.Atoi(s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return i | ||||||
|  | } | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Fredrik Lönnblad
						Fredrik Lönnblad