diff --git a/.circleci/config.yml b/.circleci/config.yml index fc17772..b5d6949 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ commands: go_test: description: "Run go test" steps: - - run: sed -i 's#github.com/cucumber/godog_test#_test#g' formatter-tests/*/* + - run: sed -i 's#github.com/cucumber/godog/internal/formatters_test#/internal/formatters_test#g' internal/formatters/formatter-tests/*/* - run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... godog: description: "Run godog" diff --git a/flags.go b/flags.go index 88aa9da..80ea8d1 100644 --- a/flags.go +++ b/flags.go @@ -10,8 +10,12 @@ import ( "time" "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/utils" ) +// repeats a space n times +var s = utils.S + var descFeaturesArgument = "Optional feature(s) to run. Can be:\n" + s(4) + "- dir " + colors.Yellow("(features/)") + "\n" + s(4) + "- feature " + colors.Yellow("(*.feature)") + "\n" + diff --git a/flags_test.go b/flags_test.go index 8520d27..5390d67 100644 --- a/flags_test.go +++ b/flags_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/formatters" ) func TestFlagsShouldRandomizeAndGenerateSeed(t *testing.T) { @@ -61,7 +62,7 @@ func TestFlagsUsageShouldIncludeFormatDescriptons(t *testing.T) { output := colors.Uncolored(&buf) // register some custom formatter - Format("custom", "custom format description", junitFunc) + Format("custom", "custom format description", formatters.JUnitFormatterFunc) var opt Options flags := FlagSet(&opt) diff --git a/fmt.go b/fmt.go index ee4dab4..5934ddb 100644 --- a/fmt.go +++ b/fmt.go @@ -6,28 +6,18 @@ import ( "strings" "unicode/utf8" - "github.com/cucumber/messages-go/v10" + "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/formatters" + internal_fmt "github.com/cucumber/godog/internal/formatters" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" ) -type registeredFormatter struct { - name string - description string - fmt FormatterFunc -} - -var formatters []*registeredFormatter - // FindFmt searches available formatters registered // and returns FormaterFunc matched by given // format name or nil otherwise func FindFmt(name string) FormatterFunc { - for _, el := range formatters { - if el.name == name { - return el.fmt - } - } - - return nil + return formatters.FindFmt(name) } // Format registers a feature suite output @@ -35,24 +25,14 @@ func FindFmt(name string) FormatterFunc { // FormatterFunc constructor function, to initialize // formatter with the output recorder. func Format(name, description string, f FormatterFunc) { - formatters = append(formatters, ®isteredFormatter{ - name: name, - fmt: f, - description: description, - }) + formatters.Format(name, description, f) } // AvailableFormatters gives a map of all // formatters registered with their name as key // and description as value func AvailableFormatters() map[string]string { - fmts := make(map[string]string, len(formatters)) - - for _, f := range formatters { - fmts[f.name] = f.description - } - - return fmts + return formatters.AvailableFormatters() } // Formatter is an interface for feature runner @@ -62,32 +42,17 @@ func AvailableFormatters() map[string]string { // suite results in different ways. These new // formatters needs to be registered with a // godog.Format function call -type Formatter interface { - TestRunStarted() - Feature(*messages.GherkinDocument, string, []byte) - Pickle(*messages.Pickle) - Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) - Failed(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition, error) - Passed(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) - Skipped(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) - Undefined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) - Pending(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) - Summary() -} +type Formatter = formatters.Formatter type storageFormatter interface { - setStorage(*storage) + SetStorage(*storage.Storage) } // FormatterFunc builds a formatter with given // suite name and io.Writer to record output -type FormatterFunc func(string, io.Writer) Formatter +type FormatterFunc = formatters.FormatterFunc -func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool { - return pickle.Steps[len(pickle.Steps)-1].Id == step.Id -} - -func printStepDefinitions(steps []*StepDefinition, w io.Writer) { +func printStepDefinitions(steps []*models.StepDefinition, w io.Writer) { var longest int for _, def := range steps { n := utf8.RuneCountInString(def.Expr.String()) @@ -98,9 +63,11 @@ func printStepDefinitions(steps []*StepDefinition, w io.Writer) { for _, def := range steps { n := utf8.RuneCountInString(def.Expr.String()) - location := def.definitionID() + location := internal_fmt.DefinitionID(def) spaces := strings.Repeat(" ", longest-n) - fmt.Fprintln(w, yellow(def.Expr.String())+spaces, blackb("# "+location)) + fmt.Fprintln(w, + colors.Yellow(def.Expr.String())+spaces, + colors.Bold(colors.Black)("# "+location)) } if len(steps) == 0 { diff --git a/formatters/fmt.go b/formatters/fmt.go new file mode 100644 index 0000000..9f56ffe --- /dev/null +++ b/formatters/fmt.go @@ -0,0 +1,79 @@ +package formatters + +import ( + "io" + + "github.com/cucumber/messages-go/v10" + + "github.com/cucumber/godog/internal/models" +) + +type registeredFormatter struct { + name string + description string + fmt FormatterFunc +} + +var registeredFormatters []*registeredFormatter + +// FindFmt searches available formatters registered +// and returns FormaterFunc matched by given +// format name or nil otherwise +func FindFmt(name string) FormatterFunc { + for _, el := range registeredFormatters { + if el.name == name { + return el.fmt + } + } + + return nil +} + +// Format registers a feature suite output +// formatter by given name, description and +// FormatterFunc constructor function, to initialize +// formatter with the output recorder. +func Format(name, description string, f FormatterFunc) { + registeredFormatters = append(registeredFormatters, ®isteredFormatter{ + name: name, + fmt: f, + description: description, + }) +} + +// AvailableFormatters gives a map of all +// formatters registered with their name as key +// and description as value +func AvailableFormatters() map[string]string { + fmts := make(map[string]string, len(registeredFormatters)) + + for _, f := range registeredFormatters { + fmts[f.name] = f.description + } + + return fmts +} + +// Formatter is an interface for feature runner +// output summary presentation. +// +// New formatters may be created to represent +// suite results in different ways. These new +// formatters needs to be registered with a +// godog.Format function call +type Formatter interface { + TestRunStarted() + Feature(*messages.GherkinDocument, string, []byte) + Pickle(*messages.Pickle) + Defined(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) + Failed(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition, error) + Passed(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) + Skipped(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) + Undefined(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) + Pending(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) + Summary() +} + +// FormatterFunc builds a formatter with given +// suite name and io.Writer to record output +type FormatterFunc func(string, io.Writer) Formatter diff --git a/formatters/fmt_test.go b/formatters/fmt_test.go new file mode 100644 index 0000000..186861c --- /dev/null +++ b/formatters/fmt_test.go @@ -0,0 +1,65 @@ +package formatters_test + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cucumber/godog" +) + +func Test_FindFmt(t *testing.T) { + cases := map[string]bool{ + "cucumber": true, + "events": true, + "junit": true, + "pretty": true, + "progress": true, + "unknown": false, + "undef": false, + } + + for name, expected := range cases { + t.Run( + name, + func(t *testing.T) { + actual := godog.FindFmt(name) + + if expected { + assert.NotNilf(t, actual, "expected %s formatter should be available", name) + } else { + assert.Nilf(t, actual, "expected %s formatter should be available", name) + } + }, + ) + } +} + +func Test_AvailableFormatters(t *testing.T) { + expected := map[string]string{ + "cucumber": "Produces cucumber JSON format output.", + "events": "Produces JSON event stream, based on spec: 0.1.0.", + "junit": "Prints junit compatible xml to stdout", + "pretty": "Prints every feature with runtime statuses.", + "progress": "Prints a character per step.", + } + + actual := godog.AvailableFormatters() + assert.Equal(t, expected, actual) +} + +func Test_Format(t *testing.T) { + actual := godog.FindFmt("Test_Format") + require.Nil(t, actual) + + godog.Format("Test_Format", "...", testFormatterFunc) + actual = godog.FindFmt("Test_Format") + + assert.NotNil(t, actual) +} + +func testFormatterFunc(suiteName string, out io.Writer) godog.Formatter { + return nil +} diff --git a/internal/formatters/fmt.go b/internal/formatters/fmt.go new file mode 100644 index 0000000..e41b5d0 --- /dev/null +++ b/internal/formatters/fmt.go @@ -0,0 +1,104 @@ +package formatters + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "runtime" + "strconv" + "strings" + + "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/utils" + "github.com/cucumber/messages-go/v10" +) + +var ( + red = colors.Red + redb = colors.Bold(colors.Red) + green = colors.Green + blackb = colors.Bold(colors.Black) + yellow = colors.Yellow + cyan = colors.Cyan + cyanb = colors.Bold(colors.Cyan) + whiteb = colors.Bold(colors.White) +) + +// repeats a space n times +var s = utils.S + +var ( + passed = models.Passed + failed = models.Failed + skipped = models.Skipped + undefined = models.Undefined + pending = models.Pending +) + +type sortFeaturesByName []*models.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 []models.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 +} + +// DefinitionID ... +func DefinitionID(sd *models.StepDefinition) string { + ptr := sd.HandlerValue.Pointer() + f := runtime.FuncForPC(ptr) + file, line := f.FileLine(ptr) + dir := filepath.Dir(file) + + fn := strings.Replace(f.Name(), dir, "", -1) + var parts []string + for _, gr := range matchFuncDefRef.FindAllStringSubmatch(fn, -1) { + parts = append(parts, strings.Trim(gr[1], "_.")) + } + if len(parts) > 0 { + // case when suite is a structure with methods + fn = strings.Join(parts, ".") + } else { + // case when steps are just plain funcs + fn = strings.Trim(fn, "_.") + } + + if pkg := os.Getenv("GODOG_TESTED_PACKAGE"); len(pkg) > 0 { + fn = strings.Replace(fn, pkg, "", 1) + fn = strings.TrimLeft(fn, ".") + fn = strings.Replace(fn, "..", ".", -1) + } + + return fmt.Sprintf("%s:%d -> %s", filepath.Base(file), line, fn) +} + +var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`) diff --git a/fmt_base.go b/internal/formatters/fmt_base.go similarity index 68% rename from fmt_base.go rename to internal/formatters/fmt_base.go index 45fcb60..d723f16 100644 --- a/fmt_base.go +++ b/internal/formatters/fmt_base.go @@ -1,4 +1,4 @@ -package godog +package formatters import ( "bytes" @@ -14,14 +14,20 @@ import ( "github.com/cucumber/messages-go/v10" "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" + "github.com/cucumber/godog/internal/utils" ) -func baseFmtFunc(suite string, out io.Writer) Formatter { - return newBaseFmt(suite, out) +// BaseFormatterFunc implements the FormatterFunc for the base formatter +func BaseFormatterFunc(suite string, out io.Writer) formatters.Formatter { + return NewBaseFmt(suite, out) } -func newBaseFmt(suite string, out io.Writer) *basefmt { - return &basefmt{ +// NewBaseFmt creates a new base formatter +func NewBaseFmt(suite string, out io.Writer) *Basefmt { + return &Basefmt{ suiteName: suite, indent: 2, out: out, @@ -29,47 +35,63 @@ func newBaseFmt(suite string, out io.Writer) *basefmt { } } -type basefmt struct { +// Basefmt ... +type Basefmt struct { suiteName string out io.Writer indent int - storage *storage + storage *storage.Storage lock *sync.Mutex } -func (f *basefmt) setStorage(st *storage) { +// SetStorage ... +func (f *Basefmt) SetStorage(st *storage.Storage) { f.lock.Lock() defer f.lock.Unlock() f.storage = st } -func (f *basefmt) TestRunStarted() {} -func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) {} -func (f *basefmt) Pickle(p *messages.Pickle) {} -func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {} -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) { +// TestRunStarted ... +func (f *Basefmt) TestRunStarted() {} + +// Feature ... +func (f *Basefmt) Feature(*messages.GherkinDocument, string, []byte) {} + +// Pickle ... +func (f *Basefmt) Pickle(*messages.Pickle) {} + +// Defined ... +func (f *Basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) {} + +// Passed ... +func (f *Basefmt) Passed(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) {} + +// Skipped ... +func (f *Basefmt) Skipped(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) {} + +// Undefined ... +func (f *Basefmt) Undefined(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) {} + +// Failed ... +func (f *Basefmt) Failed(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition, error) { } -func (f *basefmt) Summary() { +// Pending ... +func (f *Basefmt) Pending(*messages.Pickle, *messages.Pickle_PickleStep, *models.StepDefinition) {} + +// Summary ... +func (f *Basefmt) Summary() { var totalSc, passedSc, undefinedSc int var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int - pickleResults := f.storage.mustGetPickleResults() + pickleResults := f.storage.MustGetPickleResults() for _, pr := range pickleResults { - var prStatus stepResultStatus + var prStatus models.StepResultStatus totalSc++ - pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pr.PickleID) + pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pr.PickleID) if len(pickleStepResults) == 0 { prStatus = undefined @@ -130,8 +152,8 @@ func (f *basefmt) Summary() { } scenarios = append(scenarios, parts...) - testRunStartedAt := f.storage.mustGetTestRunStarted().StartedAt - elapsed := timeNowFunc().Sub(testRunStartedAt) + testRunStartedAt := f.storage.MustGetTestRunStarted().StartedAt + elapsed := utils.TimeNowFunc().Sub(testRunStartedAt) fmt.Fprintln(f.out, "") @@ -161,15 +183,16 @@ func (f *basefmt) Summary() { fmt.Fprintln(f.out, "Randomized with seed:", colors.Yellow(seed)) } - if text := f.snippets(); text != "" { + 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) snippets() string { - undefinedStepResults := f.storage.mustGetPickleStepResultsByStatus(undefined) +// Snippets ... +func (f *Basefmt) Snippets() string { + undefinedStepResults := f.storage.MustGetPickleStepResultsByStatus(undefined) if len(undefinedStepResults) == 0 { return "" } @@ -178,12 +201,12 @@ func (f *basefmt) snippets() string { var snips []undefinedSnippet // build snippets for _, u := range undefinedStepResults { - pickleStep := f.storage.mustGetPickleStep(u.PickleStepID) + pickleStep := f.storage.MustGetPickleStep(u.PickleStepID) steps := []string{pickleStep.Text} arg := pickleStep.Argument - if u.def != nil { - steps = u.def.undefined + if u.Def != nil { + steps = u.Def.Undefined arg = nil } for _, step := range steps { diff --git a/fmt_color_tag_test.go b/internal/formatters/fmt_color_tag_test.go similarity index 99% rename from fmt_color_tag_test.go rename to internal/formatters/fmt_color_tag_test.go index 6a2a19a..871e333 100644 --- a/fmt_color_tag_test.go +++ b/internal/formatters/fmt_color_tag_test.go @@ -1,4 +1,4 @@ -package godog_test +package formatters_test import ( "bytes" diff --git a/fmt_cucumber.go b/internal/formatters/fmt_cucumber.go similarity index 80% rename from fmt_cucumber.go rename to internal/formatters/fmt_cucumber.go index 10c1b5a..5f3743e 100644 --- a/fmt_cucumber.go +++ b/internal/formatters/fmt_cucumber.go @@ -1,4 +1,4 @@ -package godog +package formatters /* The specification for the formatting originated from https://www.relishapp.com/cucumber/cucumber/docs/formatters/json-output-formatter. @@ -19,22 +19,26 @@ import ( "strings" "github.com/cucumber/messages-go/v10" + + "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/models" ) func init() { - Format("cucumber", "Produces cucumber JSON format output.", cucumberFunc) + formatters.Format("cucumber", "Produces cucumber JSON format output.", CucumberFormatterFunc) } -func cucumberFunc(suite string, out io.Writer) Formatter { - return &cukefmt{basefmt: newBaseFmt(suite, out)} +// CucumberFormatterFunc implements the FormatterFunc for the cucumber formatter +func CucumberFormatterFunc(suite string, out io.Writer) formatters.Formatter { + return &cukefmt{Basefmt: NewBaseFmt(suite, out)} } type cukefmt struct { - *basefmt + *Basefmt } func (f *cukefmt) Summary() { - features := f.storage.mustGetFeatures() + features := f.storage.MustGetFeatures() res := f.buildCukeFeatures(features) @@ -46,15 +50,15 @@ func (f *cukefmt) Summary() { fmt.Fprintf(f.out, "%s\n", string(dat)) } -func (f *cukefmt) buildCukeFeatures(features []*feature) (res []cukeFeatureJSON) { +func (f *cukefmt) buildCukeFeatures(features []*models.Feature) (res []CukeFeatureJSON) { sort.Sort(sortFeaturesByName(features)) - res = make([]cukeFeatureJSON, len(features)) + res = make([]CukeFeatureJSON, len(features)) for idx, feat := range features { cukeFeature := buildCukeFeature(feat) - pickles := f.storage.mustGetPickles(feat.Uri) + pickles := f.storage.MustGetPickles(feat.Uri) sort.Sort(sortPicklesByID(pickles)) cukeFeature.Elements = f.buildCukeElements(pickles) @@ -75,8 +79,8 @@ func (f *cukefmt) buildCukeElements(pickles []*messages.Pickle) (res []cukeEleme res = make([]cukeElement, len(pickles)) for idx, pickle := range pickles { - pickleResult := f.storage.mustGetPickleResult(pickle.Id) - pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + pickleResult := f.storage.MustGetPickleResult(pickle.Id) + pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) cukeElement := f.buildCukeElement(pickle) @@ -88,12 +92,14 @@ func (f *cukefmt) buildCukeElements(pickles []*messages.Pickle) (res []cukeEleme for jdx, stepResult := range pickleStepResults { cukeStep := f.buildCukeStep(pickle, stepResult) - stepResultFinishedAt := stepResult.finishedAt + stepResultFinishedAt := stepResult.FinishedAt d := int(stepResultFinishedAt.Sub(stepStartedAt).Nanoseconds()) stepStartedAt = stepResultFinishedAt cukeStep.Result.Duration = &d - if stepResult.Status == undefined || stepResult.Status == pending || stepResult.Status == skipped { + if stepResult.Status == undefined || + stepResult.Status == pending || + stepResult.Status == skipped { cukeStep.Result.Duration = nil } @@ -157,7 +163,8 @@ type cukeElement struct { Steps []cukeStep `json:"steps,omitempty"` } -type cukeFeatureJSON struct { +// CukeFeatureJSON ... +type CukeFeatureJSON struct { URI string `json:"uri"` ID string `json:"id"` Keyword string `json:"keyword"` @@ -169,8 +176,8 @@ type cukeFeatureJSON struct { Elements []cukeElement `json:"elements,omitempty"` } -func buildCukeFeature(feat *feature) cukeFeatureJSON { - cukeFeature := cukeFeatureJSON{ +func buildCukeFeature(feat *models.Feature) CukeFeatureJSON { + cukeFeature := CukeFeatureJSON{ URI: feat.Uri, ID: makeCukeID(feat.Feature.Name), Keyword: feat.Feature.Keyword, @@ -195,8 +202,8 @@ func buildCukeFeature(feat *feature) cukeFeatureJSON { } func (f *cukefmt) buildCukeElement(pickle *messages.Pickle) (cukeElement cukeElement) { - feature := f.storage.mustGetFeature(pickle.Uri) - scenario := feature.findScenario(pickle.AstNodeIds[0]) + feature := f.storage.MustGetFeature(pickle.Uri) + scenario := feature.FindScenario(pickle.AstNodeIds[0]) cukeElement.Name = pickle.Name cukeElement.Line = int(scenario.Location.Line) @@ -214,7 +221,7 @@ func (f *cukefmt) buildCukeElement(pickle *messages.Pickle) (cukeElement cukeEle return } - example, _ := feature.findExample(pickle.AstNodeIds[1]) + example, _ := feature.FindExample(pickle.AstNodeIds[1]) for _, tag := range example.Tags { tag := cukeTag{Line: int(tag.Location.Line), Name: tag.Name} @@ -238,14 +245,14 @@ func (f *cukefmt) buildCukeElement(pickle *messages.Pickle) (cukeElement cukeEle return cukeElement } -func (f *cukefmt) buildCukeStep(pickle *messages.Pickle, stepResult pickleStepResult) (cukeStep cukeStep) { - feature := f.storage.mustGetFeature(pickle.Uri) - pickleStep := f.storage.mustGetPickleStep(stepResult.PickleStepID) - step := feature.findStep(pickleStep.AstNodeIds[0]) +func (f *cukefmt) buildCukeStep(pickle *messages.Pickle, stepResult models.PickleStepResult) (cukeStep cukeStep) { + feature := f.storage.MustGetFeature(pickle.Uri) + pickleStep := f.storage.MustGetPickleStep(stepResult.PickleStepID) + step := feature.FindStep(pickleStep.AstNodeIds[0]) line := step.Location.Line if len(pickle.AstNodeIds) == 2 { - _, row := feature.findExample(pickle.AstNodeIds[1]) + _, row := feature.FindExample(pickle.AstNodeIds[1]) line = row.Location.Line } @@ -273,13 +280,13 @@ func (f *cukefmt) buildCukeStep(pickle *messages.Pickle, stepResult pickleStepRe } } - if stepResult.def != nil { - cukeStep.Match.Location = strings.Split(stepResult.def.definitionID(), " ")[0] + if stepResult.Def != nil { + cukeStep.Match.Location = strings.Split(DefinitionID(stepResult.Def), " ")[0] } cukeStep.Result.Status = stepResult.Status.String() - if stepResult.err != nil { - cukeStep.Result.Error = stepResult.err.Error() + if stepResult.Err != nil { + cukeStep.Result.Error = stepResult.Err.Error() } if stepResult.Status == undefined || stepResult.Status == pending { diff --git a/fmt_events.go b/internal/formatters/fmt_events.go similarity index 53% rename from fmt_events.go rename to internal/formatters/fmt_events.go index a129f78..2ff0776 100644 --- a/fmt_events.go +++ b/internal/formatters/fmt_events.go @@ -1,4 +1,4 @@ -package godog +package formatters import ( "encoding/json" @@ -6,24 +6,29 @@ import ( "io" "github.com/cucumber/messages-go/v10" + + "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/utils" ) const nanoSec = 1000000 const spec = "0.1.0" func init() { - Format("events", fmt.Sprintf("Produces JSON event stream, based on spec: %s.", spec), eventsFunc) + formatters.Format("events", fmt.Sprintf("Produces JSON event stream, based on spec: %s.", spec), EventsFormatterFunc) } -func eventsFunc(suite string, out io.Writer) Formatter { - return &events{basefmt: newBaseFmt(suite, out)} +// EventsFormatterFunc implements the FormatterFunc for the events formatter +func EventsFormatterFunc(suite string, out io.Writer) formatters.Formatter { + return &eventsFormatter{Basefmt: NewBaseFmt(suite, out)} } -type events struct { - *basefmt +type eventsFormatter struct { + *Basefmt } -func (f *events) event(ev interface{}) { +func (f *eventsFormatter) event(ev interface{}) { data, err := json.Marshal(ev) if err != nil { panic(fmt.Sprintf("failed to marshal stream event: %+v - %v", ev, err)) @@ -31,8 +36,8 @@ func (f *events) event(ev interface{}) { fmt.Fprintln(f.out, string(data)) } -func (f *events) Pickle(pickle *messages.Pickle) { - f.basefmt.Pickle(pickle) +func (f *eventsFormatter) Pickle(pickle *messages.Pickle) { + f.Basefmt.Pickle(pickle) f.lock.Lock() defer f.lock.Unlock() @@ -44,7 +49,7 @@ func (f *events) Pickle(pickle *messages.Pickle) { }{ "TestCaseStarted", f.scenarioLocation(pickle), - timeNowFunc().UnixNano() / nanoSec, + utils.TimeNowFunc().UnixNano() / nanoSec, }) if len(pickle.Steps) == 0 { @@ -58,14 +63,14 @@ func (f *events) Pickle(pickle *messages.Pickle) { }{ "TestCaseFinished", f.scenarioLocation(pickle), - timeNowFunc().UnixNano() / nanoSec, + utils.TimeNowFunc().UnixNano() / nanoSec, "undefined", }) } } -func (f *events) TestRunStarted() { - f.basefmt.TestRunStarted() +func (f *eventsFormatter) TestRunStarted() { + f.Basefmt.TestRunStarted() f.lock.Lock() defer f.lock.Unlock() @@ -78,13 +83,13 @@ func (f *events) TestRunStarted() { }{ "TestRunStarted", spec, - timeNowFunc().UnixNano() / nanoSec, + utils.TimeNowFunc().UnixNano() / nanoSec, f.suiteName, }) } -func (f *events) Feature(ft *messages.GherkinDocument, p string, c []byte) { - f.basefmt.Feature(ft, p, c) +func (f *eventsFormatter) Feature(ft *messages.GherkinDocument, p string, c []byte) { + f.Basefmt.Feature(ft, p, c) f.lock.Lock() defer f.lock.Unlock() @@ -100,23 +105,23 @@ func (f *events) Feature(ft *messages.GherkinDocument, p string, c []byte) { }) } -func (f *events) Summary() { +func (f *eventsFormatter) Summary() { // @TODO: determine status status := passed - f.storage.mustGetPickleStepResultsByStatus(failed) + f.storage.MustGetPickleStepResultsByStatus(failed) - if len(f.storage.mustGetPickleStepResultsByStatus(failed)) > 0 { + if len(f.storage.MustGetPickleStepResultsByStatus(failed)) > 0 { status = failed - } else if len(f.storage.mustGetPickleStepResultsByStatus(passed)) == 0 { - if len(f.storage.mustGetPickleStepResultsByStatus(undefined)) > len(f.storage.mustGetPickleStepResultsByStatus(pending)) { + } else if len(f.storage.MustGetPickleStepResultsByStatus(passed)) == 0 { + if len(f.storage.MustGetPickleStepResultsByStatus(undefined)) > len(f.storage.MustGetPickleStepResultsByStatus(pending)) { status = undefined } else { status = pending } } - snips := f.snippets() + snips := f.Snippets() if len(snips) > 0 { snips = "You can implement step definitions for undefined steps with these snippets:\n" + snips } @@ -130,20 +135,20 @@ func (f *events) Summary() { }{ "TestRunFinished", status.String(), - timeNowFunc().UnixNano() / nanoSec, + utils.TimeNowFunc().UnixNano() / nanoSec, snips, "", // @TODO not sure that could be correctly implemented }) } -func (f *events) step(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep) { - feature := f.storage.mustGetFeature(pickle.Uri) - pickleStepResult := f.storage.mustGetPickleStepResult(pickleStep.Id) - step := feature.findStep(pickleStep.AstNodeIds[0]) +func (f *eventsFormatter) step(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep) { + feature := f.storage.MustGetFeature(pickle.Uri) + pickleStepResult := f.storage.MustGetPickleStepResult(pickleStep.Id) + step := feature.FindStep(pickleStep.AstNodeIds[0]) var errMsg string - if pickleStepResult.err != nil { - errMsg = pickleStepResult.err.Error() + if pickleStepResult.Err != nil { + errMsg = pickleStepResult.Err.Error() } f.event(&struct { Event string `json:"event"` @@ -154,7 +159,7 @@ func (f *events) step(pickle *messages.Pickle, pickleStep *messages.Pickle_Pickl }{ "TestStepFinished", fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line), - timeNowFunc().UnixNano() / nanoSec, + utils.TimeNowFunc().UnixNano() / nanoSec, pickleStepResult.Status.String(), errMsg, }) @@ -162,17 +167,11 @@ func (f *events) step(pickle *messages.Pickle, pickleStep *messages.Pickle_Pickl if isLastStep(pickle, pickleStep) { var status string - pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) for _, stepResult := range pickleStepResults { switch stepResult.Status { - case passed: - status = passed.String() - case failed: - status = failed.String() - case undefined: - status = undefined.String() - case pending: - status = pending.String() + case passed, failed, undefined, pending: + status = stepResult.Status.String() } } @@ -184,20 +183,20 @@ func (f *events) step(pickle *messages.Pickle, pickleStep *messages.Pickle_Pickl }{ "TestCaseFinished", f.scenarioLocation(pickle), - timeNowFunc().UnixNano() / nanoSec, + utils.TimeNowFunc().UnixNano() / nanoSec, status, }) } } -func (f *events) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep, def *StepDefinition) { - f.basefmt.Defined(pickle, pickleStep, def) +func (f *eventsFormatter) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep, def *models.StepDefinition) { + f.Basefmt.Defined(pickle, pickleStep, def) f.lock.Lock() defer f.lock.Unlock() - feature := f.storage.mustGetFeature(pickle.Uri) - step := feature.findStep(pickleStep.AstNodeIds[0]) + feature := f.storage.MustGetFeature(pickle.Uri) + step := feature.FindStep(pickleStep.AstNodeIds[0]) if def != nil { m := def.Expr.FindStringSubmatchIndex(pickleStep.Text)[2:] @@ -222,7 +221,7 @@ func (f *events) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_Pi }{ "StepDefinitionFound", fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line), - def.definitionID(), + DefinitionID(def), args, }) } @@ -234,12 +233,12 @@ func (f *events) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_Pi }{ "TestStepStarted", fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line), - timeNowFunc().UnixNano() / nanoSec, + utils.TimeNowFunc().UnixNano() / nanoSec, }) } -func (f *events) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Passed(pickle, step, match) +func (f *eventsFormatter) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Passed(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -247,8 +246,8 @@ func (f *events) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.step(pickle, step) } -func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Skipped(pickle, step, match) +func (f *eventsFormatter) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Skipped(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -256,8 +255,8 @@ func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.step(pickle, step) } -func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Undefined(pickle, step, match) +func (f *eventsFormatter) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Undefined(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -265,8 +264,8 @@ func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pickle f.step(pickle, step) } -func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { - f.basefmt.Failed(pickle, step, match, err) +func (f *eventsFormatter) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition, err error) { + f.Basefmt.Failed(pickle, step, match, err) f.lock.Lock() defer f.lock.Unlock() @@ -274,8 +273,8 @@ func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.step(pickle, step) } -func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Pending(pickle, step, match) +func (f *eventsFormatter) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Pending(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -283,15 +282,19 @@ func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.step(pickle, step) } -func (f *events) scenarioLocation(pickle *messages.Pickle) string { - feature := f.storage.mustGetFeature(pickle.Uri) - scenario := feature.findScenario(pickle.AstNodeIds[0]) +func (f *eventsFormatter) scenarioLocation(pickle *messages.Pickle) string { + feature := f.storage.MustGetFeature(pickle.Uri) + scenario := feature.FindScenario(pickle.AstNodeIds[0]) line := scenario.Location.Line if len(pickle.AstNodeIds) == 2 { - _, row := feature.findExample(pickle.AstNodeIds[1]) + _, row := feature.FindExample(pickle.AstNodeIds[1]) line = row.Location.Line } return fmt.Sprintf("%s:%d", pickle.Uri, line) } + +func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool { + return pickle.Steps[len(pickle.Steps)-1].Id == step.Id +} diff --git a/fmt_junit.go b/internal/formatters/fmt_junit.go similarity index 78% rename from fmt_junit.go rename to internal/formatters/fmt_junit.go index ec38cb2..8beab0d 100644 --- a/fmt_junit.go +++ b/internal/formatters/fmt_junit.go @@ -1,4 +1,4 @@ -package godog +package formatters import ( "encoding/xml" @@ -8,18 +8,22 @@ import ( "sort" "strconv" "time" + + "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/utils" ) func init() { - Format("junit", "Prints junit compatible xml to stdout", junitFunc) + formatters.Format("junit", "Prints junit compatible xml to stdout", JUnitFormatterFunc) } -func junitFunc(suite string, out io.Writer) Formatter { - return &junitFormatter{basefmt: newBaseFmt(suite, out)} +// JUnitFormatterFunc implements the FormatterFunc for the junit formatter +func JUnitFormatterFunc(suite string, out io.Writer) formatters.Formatter { + return &junitFormatter{Basefmt: NewBaseFmt(suite, out)} } type junitFormatter struct { - *basefmt + *Basefmt } func (f *junitFormatter) Summary() { @@ -41,20 +45,20 @@ func junitTimeDuration(from, to time.Time) string { return strconv.FormatFloat(to.Sub(from).Seconds(), 'f', -1, 64) } -func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { - features := f.storage.mustGetFeatures() +func (f *junitFormatter) buildJUNITPackageSuite() JunitPackageSuite { + features := f.storage.MustGetFeatures() sort.Sort(sortFeaturesByName(features)) - testRunStartedAt := f.storage.mustGetTestRunStarted().StartedAt + testRunStartedAt := f.storage.MustGetTestRunStarted().StartedAt - suite := junitPackageSuite{ + suite := JunitPackageSuite{ Name: f.suiteName, TestSuites: make([]*junitTestSuite, len(features)), - Time: junitTimeDuration(testRunStartedAt, timeNowFunc()), + Time: junitTimeDuration(testRunStartedAt, utils.TimeNowFunc()), } for idx, feature := range features { - pickles := f.storage.mustGetPickles(feature.Uri) + pickles := f.storage.MustGetPickles(feature.Uri) sort.Sort(sortPicklesByID(pickles)) ts := junitTestSuite{ @@ -74,7 +78,7 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { for idx, pickle := range pickles { tc := junitTestCase{} - pickleResult := f.storage.mustGetPickleResult(pickle.Id) + pickleResult := f.storage.MustGetPickleResult(pickle.Id) if idx == 0 { firstPickleStartedAt = pickleResult.StartedAt @@ -84,8 +88,8 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { if len(pickle.Steps) > 0 { lastStep := pickle.Steps[len(pickle.Steps)-1] - lastPickleStepResult := f.storage.mustGetPickleStepResult(lastStep.Id) - lastPickleFinishedAt = lastPickleStepResult.finishedAt + lastPickleStepResult := f.storage.MustGetPickleStepResult(lastStep.Id) + lastPickleFinishedAt = lastPickleStepResult.FinishedAt } tc.Time = junitTimeDuration(pickleResult.StartedAt, lastPickleFinishedAt) @@ -99,9 +103,9 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { ts.Tests++ suite.Tests++ - pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) for _, stepResult := range pickleStepResults { - pickleStep := f.storage.mustGetPickleStep(stepResult.PickleStepID) + pickleStep := f.storage.MustGetPickleStep(stepResult.PickleStepID) switch stepResult.Status { case passed: @@ -109,7 +113,7 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { case failed: tc.Status = failed.String() tc.Failure = &junitFailure{ - Message: fmt.Sprintf("Step %s: %s", pickleStep.Text, stepResult.err), + Message: fmt.Sprintf("Step %s: %s", pickleStep.Text, stepResult.Err), } case skipped: tc.Error = append(tc.Error, &junitError{ @@ -182,7 +186,8 @@ type junitTestSuite struct { TestCases []*junitTestCase } -type junitPackageSuite struct { +// JunitPackageSuite ... +type JunitPackageSuite struct { XMLName xml.Name `xml:"testsuites"` Name string `xml:"name,attr"` Tests int `xml:"tests,attr"` diff --git a/fmt_output_test.go b/internal/formatters/fmt_output_test.go similarity index 99% rename from fmt_output_test.go rename to internal/formatters/fmt_output_test.go index 329676e..5a9d689 100644 --- a/fmt_output_test.go +++ b/internal/formatters/fmt_output_test.go @@ -1,4 +1,4 @@ -package godog_test +package formatters_test import ( "bytes" diff --git a/fmt_pretty.go b/internal/formatters/fmt_pretty.go similarity index 80% rename from fmt_pretty.go rename to internal/formatters/fmt_pretty.go index b5bef37..db6ddf6 100644 --- a/fmt_pretty.go +++ b/internal/formatters/fmt_pretty.go @@ -1,4 +1,4 @@ -package godog +package formatters import ( "fmt" @@ -11,26 +11,29 @@ import ( "github.com/cucumber/messages-go/v10" "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/models" ) func init() { - Format("pretty", "Prints every feature with runtime statuses.", prettyFunc) + formatters.Format("pretty", "Prints every feature with runtime statuses.", PrettyFormatterFunc) } -func prettyFunc(suite string, out io.Writer) Formatter { - return &pretty{basefmt: newBaseFmt(suite, out)} +// PrettyFormatterFunc implements the FormatterFunc for the pretty formatter +func PrettyFormatterFunc(suite string, out io.Writer) formatters.Formatter { + return &pretty{Basefmt: NewBaseFmt(suite, out)} } var outlinePlaceholderRegexp = regexp.MustCompile("<[^>]+>") // a built in default pretty formatter type pretty struct { - *basefmt + *Basefmt firstFeature *bool } func (f *pretty) TestRunStarted() { - f.basefmt.TestRunStarted() + f.Basefmt.TestRunStarted() f.lock.Lock() defer f.lock.Unlock() @@ -48,7 +51,7 @@ func (f *pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) { *f.firstFeature = false f.lock.Unlock() - f.basefmt.Feature(gd, p, c) + f.Basefmt.Feature(gd, p, c) f.lock.Lock() defer f.lock.Unlock() @@ -58,7 +61,7 @@ func (f *pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) { // Pickle takes a gherkin node for formatting func (f *pretty) Pickle(pickle *messages.Pickle) { - f.basefmt.Pickle(pickle) + f.Basefmt.Pickle(pickle) f.lock.Lock() defer f.lock.Unlock() @@ -69,8 +72,8 @@ func (f *pretty) Pickle(pickle *messages.Pickle) { } } -func (f *pretty) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Passed(pickle, step, match) +func (f *pretty) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Passed(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -78,8 +81,8 @@ func (f *pretty) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.printStep(pickle, step) } -func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Skipped(pickle, step, match) +func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Skipped(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -87,8 +90,8 @@ func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.printStep(pickle, step) } -func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Undefined(pickle, step, match) +func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Undefined(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -96,8 +99,8 @@ func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pickle f.printStep(pickle, step) } -func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { - f.basefmt.Failed(pickle, step, match, err) +func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition, err error) { + f.Basefmt.Failed(pickle, step, match, err) f.lock.Lock() defer f.lock.Unlock() @@ -105,8 +108,8 @@ func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.printStep(pickle, step) } -func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Pending(pickle, step, match) +func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Pending(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -132,9 +135,9 @@ func keywordAndName(keyword, name string) string { } func (f *pretty) scenarioLengths(pickle *messages.Pickle) (scenarioHeaderLength int, maxLength int) { - feature := f.storage.mustGetFeature(pickle.Uri) - astScenario := feature.findScenario(pickle.AstNodeIds[0]) - astBackground := feature.findBackground(pickle.AstNodeIds[0]) + feature := f.storage.MustGetFeature(pickle.Uri) + astScenario := feature.FindScenario(pickle.AstNodeIds[0]) + astBackground := feature.FindBackground(pickle.AstNodeIds[0]) scenarioHeaderLength = f.lengthPickle(astScenario.Keyword, astScenario.Name) maxLength = f.longestStep(astScenario.Steps, scenarioHeaderLength) @@ -147,16 +150,16 @@ func (f *pretty) scenarioLengths(pickle *messages.Pickle) (scenarioHeaderLength } func (f *pretty) printScenarioHeader(pickle *messages.Pickle, astScenario *messages.GherkinDocument_Feature_Scenario, spaceFilling int) { - feature := f.storage.mustGetFeature(pickle.Uri) + feature := f.storage.MustGetFeature(pickle.Uri) text := s(f.indent) + keywordAndName(astScenario.Keyword, astScenario.Name) text += s(spaceFilling) + line(feature.Uri, astScenario.Location) fmt.Fprintln(f.out, "\n"+text) } func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) { - feature := f.storage.mustGetFeature(pickle.Uri) - astScenario := feature.findScenario(pickle.AstNodeIds[0]) - astBackground := feature.findBackground(pickle.AstNodeIds[0]) + feature := f.storage.MustGetFeature(pickle.Uri) + astScenario := feature.FindScenario(pickle.AstNodeIds[0]) + astBackground := feature.FindBackground(pickle.AstNodeIds[0]) scenarioHeaderLength, maxLength := f.scenarioLengths(pickle) @@ -170,7 +173,7 @@ func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) { // do not print scenario headers and examples multiple times if len(astScenario.Examples) > 0 { - exampleTable, exampleRow := feature.findExample(pickle.AstNodeIds[1]) + exampleTable, exampleRow := feature.FindExample(pickle.AstNodeIds[1]) firstExampleRow := exampleTable.TableBody[0].Id == exampleRow.Id firstExamplesTable := astScenario.Examples[0].Location.Line == exampleTable.Location.Line @@ -197,45 +200,45 @@ func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) { // Summary sumarize the feature formatter output func (f *pretty) Summary() { - failedStepResults := f.storage.mustGetPickleStepResultsByStatus(failed) + failedStepResults := f.storage.MustGetPickleStepResultsByStatus(failed) if len(failedStepResults) > 0 { fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n") sort.Sort(sortPickleStepResultsByPickleStepID(failedStepResults)) for _, fail := range failedStepResults { - pickle := f.storage.mustGetPickle(fail.PickleID) - pickleStep := f.storage.mustGetPickleStep(fail.PickleStepID) - feature := f.storage.mustGetFeature(pickle.Uri) + pickle := f.storage.MustGetPickle(fail.PickleID) + pickleStep := f.storage.MustGetPickleStep(fail.PickleStepID) + feature := f.storage.MustGetFeature(pickle.Uri) - astScenario := feature.findScenario(pickle.AstNodeIds[0]) + astScenario := feature.FindScenario(pickle.AstNodeIds[0]) scenarioDesc := fmt.Sprintf("%s: %s", astScenario.Keyword, pickle.Name) - astStep := feature.findStep(pickleStep.AstNodeIds[0]) + astStep := feature.FindStep(pickleStep.AstNodeIds[0]) stepDesc := strings.TrimSpace(astStep.Keyword) + " " + pickleStep.Text fmt.Fprintln(f.out, s(f.indent)+red(scenarioDesc)+line(feature.Uri, astScenario.Location)) fmt.Fprintln(f.out, s(f.indent*2)+red(stepDesc)+line(feature.Uri, astStep.Location)) - fmt.Fprintln(f.out, s(f.indent*3)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n") + fmt.Fprintln(f.out, s(f.indent*3)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.Err))+"\n") } } - f.basefmt.Summary() + f.Basefmt.Summary() } func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps int) { var errorMsg string var clr = green - feature := f.storage.mustGetFeature(pickle.Uri) - astScenario := feature.findScenario(pickle.AstNodeIds[0]) + feature := f.storage.MustGetFeature(pickle.Uri) + astScenario := feature.FindScenario(pickle.AstNodeIds[0]) scenarioHeaderLength, maxLength := f.scenarioLengths(pickle) - exampleTable, exampleRow := feature.findExample(pickle.AstNodeIds[1]) + exampleTable, exampleRow := feature.FindExample(pickle.AstNodeIds[1]) printExampleHeader := exampleTable.TableBody[0].Id == exampleRow.Id firstExamplesTable := astScenario.Examples[0].Location.Line == exampleTable.Location.Line - pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) firstExecutedScenarioStep := len(pickleStepResults) == backgroundSteps+1 if firstExamplesTable && printExampleHeader && firstExecutedScenarioStep { @@ -257,10 +260,10 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in // determine example row status switch { case result.Status == failed: - errorMsg = result.err.Error() - clr = result.Status.clr() + errorMsg = result.Err.Error() + clr = result.Status.Color() case result.Status == undefined || result.Status == pending: - clr = result.Status.clr() + clr = result.Status.Color() case result.Status == skipped && clr == nil: clr = cyan } @@ -268,11 +271,11 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in if firstExamplesTable && printExampleHeader { // in first example, we need to print steps - pickleStep := f.storage.mustGetPickleStep(result.PickleStepID) - astStep := feature.findStep(pickleStep.AstNodeIds[0]) + pickleStep := f.storage.MustGetPickleStep(result.PickleStepID) + astStep := feature.FindStep(pickleStep.AstNodeIds[0]) var text = "" - if result.def != nil { + if result.Def != nil { if m := outlinePlaceholderRegexp.FindAllStringIndex(astStep.Text, -1); len(m) > 0 { var pos int for i := 0; i < len(m); i++ { @@ -290,7 +293,7 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in stepLength := f.lengthPickleStep(astStep.Keyword, astStep.Text) text += s(maxLength - stepLength) - text += " " + blackb("# "+result.def.definitionID()) + text += " " + blackb("# "+DefinitionID(result.Def)) } // print the step outline @@ -340,10 +343,10 @@ func (f *pretty) printTableHeader(row *messages.GherkinDocument_Feature_TableRow } func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep) { - feature := f.storage.mustGetFeature(pickle.Uri) - astBackground := feature.findBackground(pickle.AstNodeIds[0]) - astScenario := feature.findScenario(pickle.AstNodeIds[0]) - astStep := feature.findStep(pickleStep.AstNodeIds[0]) + feature := f.storage.MustGetFeature(pickle.Uri) + astBackground := feature.FindBackground(pickle.AstNodeIds[0]) + astScenario := feature.FindScenario(pickle.AstNodeIds[0]) + astStep := feature.FindStep(pickleStep.AstNodeIds[0]) var astBackgroundStep bool var firstExecutedBackgroundStep bool @@ -360,7 +363,7 @@ func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.Pickle_ } } - firstPickle := feature.pickles[0].Id == pickle.Id + firstPickle := feature.Pickles[0].Id == pickle.Id if astBackgroundStep && !firstPickle { return @@ -383,11 +386,11 @@ func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.Pickle_ f.printScenarioHeader(pickle, astScenario, maxLength-scenarioHeaderLength) } - pickleStepResult := f.storage.mustGetPickleStepResult(pickleStep.Id) - text := s(f.indent*2) + pickleStepResult.Status.clr()(strings.TrimSpace(astStep.Keyword)) + " " + pickleStepResult.Status.clr()(pickleStep.Text) - if pickleStepResult.def != nil { + pickleStepResult := f.storage.MustGetPickleStepResult(pickleStep.Id) + text := s(f.indent*2) + pickleStepResult.Status.Color()(strings.TrimSpace(astStep.Keyword)) + " " + pickleStepResult.Status.Color()(pickleStep.Text) + if pickleStepResult.Def != nil { text += s(maxLength - stepLength + 1) - text += blackb("# " + pickleStepResult.def.definitionID()) + text += blackb("# " + DefinitionID(pickleStepResult.Def)) } fmt.Fprintln(f.out, text) @@ -399,8 +402,8 @@ func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.Pickle_ f.printDocString(docString) } - if pickleStepResult.err != nil { - fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", pickleStepResult.err))) + if pickleStepResult.Err != nil { + fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", pickleStepResult.Err))) } if pickleStepResult.Status == pending { diff --git a/fmt_progress.go b/internal/formatters/fmt_progress.go similarity index 63% rename from fmt_progress.go rename to internal/formatters/fmt_progress.go index 78c9d8d..3f626fd 100644 --- a/fmt_progress.go +++ b/internal/formatters/fmt_progress.go @@ -1,4 +1,4 @@ -package godog +package formatters import ( "fmt" @@ -8,23 +8,27 @@ import ( "strings" "github.com/cucumber/messages-go/v10" + + "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/models" ) func init() { - Format("progress", "Prints a character per step.", progressFunc) + formatters.Format("progress", "Prints a character per step.", ProgressFormatterFunc) } -func progressFunc(suite string, out io.Writer) Formatter { +// ProgressFormatterFunc implements the FormatterFunc for the progress formatter +func ProgressFormatterFunc(suite string, out io.Writer) formatters.Formatter { steps := 0 return &progress{ - basefmt: newBaseFmt(suite, out), + Basefmt: NewBaseFmt(suite, out), stepsPerRow: 70, steps: &steps, } } type progress struct { - *basefmt + *Basefmt stepsPerRow int steps *int } @@ -41,20 +45,20 @@ func (f *progress) Summary() { var failedStepsOutput []string - failedSteps := f.storage.mustGetPickleStepResultsByStatus(failed) + failedSteps := f.storage.MustGetPickleStepResultsByStatus(failed) sort.Sort(sortPickleStepResultsByPickleStepID(failedSteps)) for _, sr := range failedSteps { if sr.Status == failed { - pickle := f.storage.mustGetPickle(sr.PickleID) - pickleStep := f.storage.mustGetPickleStep(sr.PickleStepID) - feature := f.storage.mustGetFeature(pickle.Uri) + pickle := f.storage.MustGetPickle(sr.PickleID) + pickleStep := f.storage.MustGetPickleStep(sr.PickleStepID) + feature := f.storage.MustGetFeature(pickle.Uri) - sc := feature.findScenario(pickle.AstNodeIds[0]) + sc := feature.FindScenario(pickle.AstNodeIds[0]) scenarioDesc := fmt.Sprintf("%s: %s", sc.Keyword, pickle.Name) scenarioLine := fmt.Sprintf("%s:%d", pickle.Uri, sc.Location.Line) - step := feature.findStep(pickleStep.AstNodeIds[0]) + step := feature.FindStep(pickleStep.AstNodeIds[0]) stepDesc := strings.TrimSpace(step.Keyword) + " " + pickleStep.Text stepLine := fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line) @@ -62,7 +66,7 @@ func (f *progress) Summary() { failedStepsOutput, s(2)+red(scenarioDesc)+blackb(" # "+scenarioLine), s(4)+red(stepDesc)+blackb(" # "+stepLine), - s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", sr.err)), + s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", sr.Err)), "", ) } @@ -74,11 +78,11 @@ func (f *progress) Summary() { } fmt.Fprintln(f.out, "") - f.basefmt.Summary() + f.Basefmt.Summary() } func (f *progress) step(pickleStepID string) { - pickleStepResult := f.storage.mustGetPickleStepResult(pickleStepID) + pickleStepResult := f.storage.MustGetPickleStepResult(pickleStepID) switch pickleStepResult.Status { case passed: @@ -100,8 +104,8 @@ func (f *progress) step(pickleStepID string) { } } -func (f *progress) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Passed(pickle, step, match) +func (f *progress) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Passed(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -109,8 +113,8 @@ func (f *progress) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleS f.step(step.Id) } -func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Skipped(pickle, step, match) +func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Skipped(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -118,8 +122,8 @@ func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_Pickle f.step(step.Id) } -func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Undefined(pickle, step, match) +func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Undefined(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() @@ -127,8 +131,8 @@ func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pick f.step(step.Id) } -func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { - f.basefmt.Failed(pickle, step, match, err) +func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition, err error) { + f.Basefmt.Failed(pickle, step, match, err) f.lock.Lock() defer f.lock.Unlock() @@ -136,8 +140,8 @@ func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleS f.step(step.Id) } -func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.basefmt.Pending(pickle, step, match) +func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *models.StepDefinition) { + f.Basefmt.Pending(pickle, step, match) f.lock.Lock() defer f.lock.Unlock() diff --git a/formatter-tests/cucumber/empty b/internal/formatters/formatter-tests/cucumber/empty similarity index 100% rename from formatter-tests/cucumber/empty rename to internal/formatters/formatter-tests/cucumber/empty diff --git a/formatter-tests/cucumber/empty_with_description b/internal/formatters/formatter-tests/cucumber/empty_with_description similarity index 100% rename from formatter-tests/cucumber/empty_with_description rename to internal/formatters/formatter-tests/cucumber/empty_with_description diff --git a/formatter-tests/cucumber/empty_with_single_scenario_without_steps b/internal/formatters/formatter-tests/cucumber/empty_with_single_scenario_without_steps similarity index 100% rename from formatter-tests/cucumber/empty_with_single_scenario_without_steps rename to internal/formatters/formatter-tests/cucumber/empty_with_single_scenario_without_steps diff --git a/formatter-tests/cucumber/empty_with_single_scenario_without_steps_and_description b/internal/formatters/formatter-tests/cucumber/empty_with_single_scenario_without_steps_and_description similarity index 100% rename from formatter-tests/cucumber/empty_with_single_scenario_without_steps_and_description rename to internal/formatters/formatter-tests/cucumber/empty_with_single_scenario_without_steps_and_description diff --git a/formatter-tests/cucumber/scenario_outline b/internal/formatters/formatter-tests/cucumber/scenario_outline similarity index 100% rename from formatter-tests/cucumber/scenario_outline rename to internal/formatters/formatter-tests/cucumber/scenario_outline diff --git a/formatter-tests/cucumber/scenario_with_background b/internal/formatters/formatter-tests/cucumber/scenario_with_background similarity index 100% rename from formatter-tests/cucumber/scenario_with_background rename to internal/formatters/formatter-tests/cucumber/scenario_with_background diff --git a/formatter-tests/cucumber/scenario_without_steps_with_background b/internal/formatters/formatter-tests/cucumber/scenario_without_steps_with_background similarity index 100% rename from formatter-tests/cucumber/scenario_without_steps_with_background rename to internal/formatters/formatter-tests/cucumber/scenario_without_steps_with_background diff --git a/formatter-tests/cucumber/single_scenario_with_passing_step b/internal/formatters/formatter-tests/cucumber/single_scenario_with_passing_step similarity index 100% rename from formatter-tests/cucumber/single_scenario_with_passing_step rename to internal/formatters/formatter-tests/cucumber/single_scenario_with_passing_step diff --git a/formatter-tests/cucumber/some_scenarions_including_failing b/internal/formatters/formatter-tests/cucumber/some_scenarions_including_failing similarity index 100% rename from formatter-tests/cucumber/some_scenarions_including_failing rename to internal/formatters/formatter-tests/cucumber/some_scenarions_including_failing diff --git a/formatter-tests/cucumber/two_scenarios_with_background_fail b/internal/formatters/formatter-tests/cucumber/two_scenarios_with_background_fail similarity index 100% rename from formatter-tests/cucumber/two_scenarios_with_background_fail rename to internal/formatters/formatter-tests/cucumber/two_scenarios_with_background_fail diff --git a/formatter-tests/cucumber/with_few_empty_scenarios b/internal/formatters/formatter-tests/cucumber/with_few_empty_scenarios similarity index 100% rename from formatter-tests/cucumber/with_few_empty_scenarios rename to internal/formatters/formatter-tests/cucumber/with_few_empty_scenarios diff --git a/formatter-tests/events/empty b/internal/formatters/formatter-tests/events/empty similarity index 100% rename from formatter-tests/events/empty rename to internal/formatters/formatter-tests/events/empty diff --git a/formatter-tests/events/empty_with_description b/internal/formatters/formatter-tests/events/empty_with_description similarity index 100% rename from formatter-tests/events/empty_with_description rename to internal/formatters/formatter-tests/events/empty_with_description diff --git a/formatter-tests/events/empty_with_single_scenario_without_steps b/internal/formatters/formatter-tests/events/empty_with_single_scenario_without_steps similarity index 100% rename from formatter-tests/events/empty_with_single_scenario_without_steps rename to internal/formatters/formatter-tests/events/empty_with_single_scenario_without_steps diff --git a/formatter-tests/events/empty_with_single_scenario_without_steps_and_description b/internal/formatters/formatter-tests/events/empty_with_single_scenario_without_steps_and_description similarity index 100% rename from formatter-tests/events/empty_with_single_scenario_without_steps_and_description rename to internal/formatters/formatter-tests/events/empty_with_single_scenario_without_steps_and_description diff --git a/formatter-tests/events/scenario_outline b/internal/formatters/formatter-tests/events/scenario_outline similarity index 83% rename from formatter-tests/events/scenario_outline rename to internal/formatters/formatter-tests/events/scenario_outline index bfb9dc6..44a976d 100644 --- a/formatter-tests/events/scenario_outline +++ b/internal/formatters/formatter-tests/events/scenario_outline @@ -1,57 +1,57 @@ {"event":"TestRunStarted","version":"0.1.0","timestamp":-6795364578871,"suite":"events"} {"event":"TestSource","location":"formatter-tests/features/scenario_outline.feature:2","source":"@outline @tag\nFeature: outline\n\n @scenario\n Scenario Outline: outline\n Given passing step\n When passing step\n Then odd \u003codd\u003e and even \u003ceven\u003e number\n\n @tagged\n Examples: tagged\n | odd | even |\n | 1 | 2 |\n | 2 | 0 |\n | 3 | 11 |\n\n @tag2\n Examples:\n | odd | even |\n | 1 | 14 |\n | 3 | 9 |\n"} {"event":"TestCaseStarted","location":"formatter-tests/features/scenario_outline.feature:13","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog/internal/formatters_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871,"status":"passed"} {"event":"TestCaseFinished","location":"formatter-tests/features/scenario_outline.feature:13","timestamp":-6795364578871,"status":"passed"} {"event":"TestCaseStarted","location":"formatter-tests/features/scenario_outline.feature:14","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog/internal/formatters_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871,"status":"failed","summary":"2 is not odd"} {"event":"TestCaseFinished","location":"formatter-tests/features/scenario_outline.feature:14","timestamp":-6795364578871,"status":"failed"} {"event":"TestCaseStarted","location":"formatter-tests/features/scenario_outline.feature:15","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog/internal/formatters_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871,"status":"failed","summary":"11 is not even"} {"event":"TestCaseFinished","location":"formatter-tests/features/scenario_outline.feature:15","timestamp":-6795364578871,"status":"failed"} {"event":"TestCaseStarted","location":"formatter-tests/features/scenario_outline.feature:20","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog/internal/formatters_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871,"status":"passed"} {"event":"TestCaseFinished","location":"formatter-tests/features/scenario_outline.feature:20","timestamp":-6795364578871,"status":"passed"} {"event":"TestCaseStarted","location":"formatter-tests/features/scenario_outline.feature:21","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:7","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:8","definition_id":"fmt_output_test.go:103 -\u003e github.com/cucumber/godog/internal/formatters_test.oddEvenStepDef","arguments":[[4,5],[5,15]]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_outline.feature:8","timestamp":-6795364578871,"status":"failed","summary":"9 is not even"} {"event":"TestCaseFinished","location":"formatter-tests/features/scenario_outline.feature:21","timestamp":-6795364578871,"status":"failed"} diff --git a/formatter-tests/events/scenario_with_background b/internal/formatters/formatter-tests/events/scenario_with_background similarity index 84% rename from formatter-tests/events/scenario_with_background rename to internal/formatters/formatter-tests/events/scenario_with_background index 629e76a..0b15684 100644 --- a/formatter-tests/events/scenario_with_background +++ b/internal/formatters/formatter-tests/events/scenario_with_background @@ -1,16 +1,16 @@ {"event":"TestRunStarted","version":"0.1.0","timestamp":-6795364578871,"suite":"events"} {"event":"TestSource","location":"formatter-tests/features/scenario_with_background.feature:1","source":"Feature: single scenario with background\n\n Background: named\n Given passing step\n And passing step\n\n Scenario: scenario\n When passing step\n Then passing step\n"} {"event":"TestCaseStarted","location":"formatter-tests/features/scenario_with_background.feature:7","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_with_background.feature:4","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_with_background.feature:4","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:5","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:5","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_with_background.feature:5","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_with_background.feature:5","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:8","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:8","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_with_background.feature:8","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_with_background.feature:8","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:9","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_with_background.feature:9","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/scenario_with_background.feature:9","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/scenario_with_background.feature:9","timestamp":-6795364578871,"status":"passed"} {"event":"TestCaseFinished","location":"formatter-tests/features/scenario_with_background.feature:7","timestamp":-6795364578871,"status":"passed"} diff --git a/formatter-tests/events/scenario_without_steps_with_background b/internal/formatters/formatter-tests/events/scenario_without_steps_with_background similarity index 100% rename from formatter-tests/events/scenario_without_steps_with_background rename to internal/formatters/formatter-tests/events/scenario_without_steps_with_background diff --git a/formatter-tests/events/single_scenario_with_passing_step b/internal/formatters/formatter-tests/events/single_scenario_with_passing_step similarity index 90% rename from formatter-tests/events/single_scenario_with_passing_step rename to internal/formatters/formatter-tests/events/single_scenario_with_passing_step index b487e60..bac2f33 100644 --- a/formatter-tests/events/single_scenario_with_passing_step +++ b/internal/formatters/formatter-tests/events/single_scenario_with_passing_step @@ -1,7 +1,7 @@ {"event":"TestRunStarted","version":"0.1.0","timestamp":-6795364578871,"suite":"events"} {"event":"TestSource","location":"formatter-tests/features/single_scenario_with_passing_step.feature:1","source":"Feature: single passing scenario\n describes\n a single scenario\n feature\n\n Scenario: one step passing\n Given a passing step\n"} {"event":"TestCaseStarted","location":"formatter-tests/features/single_scenario_with_passing_step.feature:6","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/single_scenario_with_passing_step.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/single_scenario_with_passing_step.feature:7","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/single_scenario_with_passing_step.feature:7","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/single_scenario_with_passing_step.feature:7","timestamp":-6795364578871,"status":"passed"} {"event":"TestCaseFinished","location":"formatter-tests/features/single_scenario_with_passing_step.feature:6","timestamp":-6795364578871,"status":"passed"} diff --git a/formatter-tests/events/some_scenarions_including_failing b/internal/formatters/formatter-tests/events/some_scenarions_including_failing similarity index 86% rename from formatter-tests/events/some_scenarions_including_failing rename to internal/formatters/formatter-tests/events/some_scenarions_including_failing index d4bee67..d220e14 100644 --- a/formatter-tests/events/some_scenarions_including_failing +++ b/internal/formatters/formatter-tests/events/some_scenarions_including_failing @@ -1,28 +1,28 @@ {"event":"TestRunStarted","version":"0.1.0","timestamp":-6795364578871,"suite":"events"} {"event":"TestSource","location":"formatter-tests/features/some_scenarions_including_failing.feature:1","source":"Feature: some scenarios\n\n Scenario: failing\n Given passing step\n When failing step\n Then passing step\n\n Scenario: pending\n When pending step\n Then passing step\n\n Scenario: undefined\n When undefined\n Then passing step\n"} {"event":"TestCaseStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:3","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:4","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:4","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:5","definition_id":"fmt_output_test.go:117 -\u003e github.com/cucumber/godog_test.failingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:5","definition_id":"fmt_output_test.go:117 -\u003e github.com/cucumber/godog/internal/formatters_test.failingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:5","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:5","timestamp":-6795364578871,"status":"failed","summary":"step failed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:6","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:6","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:6","timestamp":-6795364578871,"status":"skipped"} {"event":"TestCaseFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:3","timestamp":-6795364578871,"status":"failed"} {"event":"TestCaseStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:8","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:9","definition_id":"fmt_output_test.go:115 -\u003e github.com/cucumber/godog_test.pendingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:9","definition_id":"fmt_output_test.go:115 -\u003e github.com/cucumber/godog/internal/formatters_test.pendingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:9","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:9","timestamp":-6795364578871,"status":"pending"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:10","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:10","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:10","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:10","timestamp":-6795364578871,"status":"skipped"} {"event":"TestCaseFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:8","timestamp":-6795364578871,"status":"pending"} {"event":"TestCaseStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:12","timestamp":-6795364578871} {"event":"TestStepStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:13","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:13","timestamp":-6795364578871,"status":"undefined"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:14","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/some_scenarions_including_failing.feature:14","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/some_scenarions_including_failing.feature:14","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:14","timestamp":-6795364578871,"status":"skipped"} {"event":"TestCaseFinished","location":"formatter-tests/features/some_scenarions_including_failing.feature:12","timestamp":-6795364578871,"status":"undefined"} diff --git a/formatter-tests/events/two_scenarios_with_background_fail b/internal/formatters/formatter-tests/events/two_scenarios_with_background_fail similarity index 83% rename from formatter-tests/events/two_scenarios_with_background_fail rename to internal/formatters/formatter-tests/events/two_scenarios_with_background_fail index faa05b6..633f996 100644 --- a/formatter-tests/events/two_scenarios_with_background_fail +++ b/internal/formatters/formatter-tests/events/two_scenarios_with_background_fail @@ -1,27 +1,27 @@ {"event":"TestRunStarted","version":"0.1.0","timestamp":-6795364578871,"suite":"events"} {"event":"TestSource","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:1","source":"Feature: two scenarios with background fail\n\n Background:\n Given passing step\n And failing step\n\n Scenario: one\n When passing step\n Then passing step\n\n Scenario: two\n Then passing step\n"} {"event":"TestCaseStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:7","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","definition_id":"fmt_output_test.go:117 -\u003e github.com/cucumber/godog_test.failingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","definition_id":"fmt_output_test.go:117 -\u003e github.com/cucumber/godog/internal/formatters_test.failingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","timestamp":-6795364578871,"status":"failed","summary":"step failed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:8","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:8","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:8","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:8","timestamp":-6795364578871,"status":"skipped"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:9","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:9","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:9","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:9","timestamp":-6795364578871,"status":"skipped"} {"event":"TestCaseFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:7","timestamp":-6795364578871,"status":"failed"} {"event":"TestCaseStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:11","timestamp":-6795364578871} -{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:4","timestamp":-6795364578871,"status":"passed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","definition_id":"fmt_output_test.go:117 -\u003e github.com/cucumber/godog_test.failingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","definition_id":"fmt_output_test.go:117 -\u003e github.com/cucumber/godog/internal/formatters_test.failingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:5","timestamp":-6795364578871,"status":"failed","summary":"step failed"} -{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:12","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog_test.passingStepDef","arguments":[]} +{"event":"StepDefinitionFound","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:12","definition_id":"fmt_output_test.go:101 -\u003e github.com/cucumber/godog/internal/formatters_test.passingStepDef","arguments":[]} {"event":"TestStepStarted","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:12","timestamp":-6795364578871} {"event":"TestStepFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:12","timestamp":-6795364578871,"status":"skipped"} {"event":"TestCaseFinished","location":"formatter-tests/features/two_scenarios_with_background_fail.feature:11","timestamp":-6795364578871,"status":"failed"} diff --git a/formatter-tests/events/with_few_empty_scenarios b/internal/formatters/formatter-tests/events/with_few_empty_scenarios similarity index 100% rename from formatter-tests/events/with_few_empty_scenarios rename to internal/formatters/formatter-tests/events/with_few_empty_scenarios diff --git a/formatter-tests/features/empty.feature b/internal/formatters/formatter-tests/features/empty.feature similarity index 100% rename from formatter-tests/features/empty.feature rename to internal/formatters/formatter-tests/features/empty.feature diff --git a/formatter-tests/features/empty_with_description.feature b/internal/formatters/formatter-tests/features/empty_with_description.feature similarity index 100% rename from formatter-tests/features/empty_with_description.feature rename to internal/formatters/formatter-tests/features/empty_with_description.feature diff --git a/formatter-tests/features/empty_with_single_scenario_without_steps.feature b/internal/formatters/formatter-tests/features/empty_with_single_scenario_without_steps.feature similarity index 100% rename from formatter-tests/features/empty_with_single_scenario_without_steps.feature rename to internal/formatters/formatter-tests/features/empty_with_single_scenario_without_steps.feature diff --git a/formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature b/internal/formatters/formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature similarity index 100% rename from formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature rename to internal/formatters/formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature diff --git a/formatter-tests/features/scenario_outline.feature b/internal/formatters/formatter-tests/features/scenario_outline.feature similarity index 100% rename from formatter-tests/features/scenario_outline.feature rename to internal/formatters/formatter-tests/features/scenario_outline.feature diff --git a/formatter-tests/features/scenario_with_background.feature b/internal/formatters/formatter-tests/features/scenario_with_background.feature similarity index 100% rename from formatter-tests/features/scenario_with_background.feature rename to internal/formatters/formatter-tests/features/scenario_with_background.feature diff --git a/formatter-tests/features/scenario_without_steps_with_background.feature b/internal/formatters/formatter-tests/features/scenario_without_steps_with_background.feature similarity index 100% rename from formatter-tests/features/scenario_without_steps_with_background.feature rename to internal/formatters/formatter-tests/features/scenario_without_steps_with_background.feature diff --git a/formatter-tests/features/single_scenario_with_passing_step.feature b/internal/formatters/formatter-tests/features/single_scenario_with_passing_step.feature similarity index 100% rename from formatter-tests/features/single_scenario_with_passing_step.feature rename to internal/formatters/formatter-tests/features/single_scenario_with_passing_step.feature diff --git a/formatter-tests/features/some_scenarions_including_failing.feature b/internal/formatters/formatter-tests/features/some_scenarions_including_failing.feature similarity index 100% rename from formatter-tests/features/some_scenarions_including_failing.feature rename to internal/formatters/formatter-tests/features/some_scenarions_including_failing.feature diff --git a/formatter-tests/features/two_scenarios_with_background_fail.feature b/internal/formatters/formatter-tests/features/two_scenarios_with_background_fail.feature similarity index 100% rename from formatter-tests/features/two_scenarios_with_background_fail.feature rename to internal/formatters/formatter-tests/features/two_scenarios_with_background_fail.feature diff --git a/formatter-tests/features/with_few_empty_scenarios.feature b/internal/formatters/formatter-tests/features/with_few_empty_scenarios.feature similarity index 100% rename from formatter-tests/features/with_few_empty_scenarios.feature rename to internal/formatters/formatter-tests/features/with_few_empty_scenarios.feature diff --git a/formatter-tests/junit/empty b/internal/formatters/formatter-tests/junit/empty similarity index 100% rename from formatter-tests/junit/empty rename to internal/formatters/formatter-tests/junit/empty diff --git a/formatter-tests/junit/empty_with_description b/internal/formatters/formatter-tests/junit/empty_with_description similarity index 100% rename from formatter-tests/junit/empty_with_description rename to internal/formatters/formatter-tests/junit/empty_with_description diff --git a/formatter-tests/junit/empty_with_single_scenario_without_steps b/internal/formatters/formatter-tests/junit/empty_with_single_scenario_without_steps similarity index 100% rename from formatter-tests/junit/empty_with_single_scenario_without_steps rename to internal/formatters/formatter-tests/junit/empty_with_single_scenario_without_steps diff --git a/formatter-tests/junit/empty_with_single_scenario_without_steps_and_description b/internal/formatters/formatter-tests/junit/empty_with_single_scenario_without_steps_and_description similarity index 100% rename from formatter-tests/junit/empty_with_single_scenario_without_steps_and_description rename to internal/formatters/formatter-tests/junit/empty_with_single_scenario_without_steps_and_description diff --git a/formatter-tests/junit/scenario_outline b/internal/formatters/formatter-tests/junit/scenario_outline similarity index 100% rename from formatter-tests/junit/scenario_outline rename to internal/formatters/formatter-tests/junit/scenario_outline diff --git a/formatter-tests/junit/scenario_with_background b/internal/formatters/formatter-tests/junit/scenario_with_background similarity index 100% rename from formatter-tests/junit/scenario_with_background rename to internal/formatters/formatter-tests/junit/scenario_with_background diff --git a/formatter-tests/junit/scenario_without_steps_with_background b/internal/formatters/formatter-tests/junit/scenario_without_steps_with_background similarity index 100% rename from formatter-tests/junit/scenario_without_steps_with_background rename to internal/formatters/formatter-tests/junit/scenario_without_steps_with_background diff --git a/formatter-tests/junit/single_scenario_with_passing_step b/internal/formatters/formatter-tests/junit/single_scenario_with_passing_step similarity index 100% rename from formatter-tests/junit/single_scenario_with_passing_step rename to internal/formatters/formatter-tests/junit/single_scenario_with_passing_step diff --git a/formatter-tests/junit/some_scenarions_including_failing b/internal/formatters/formatter-tests/junit/some_scenarions_including_failing similarity index 100% rename from formatter-tests/junit/some_scenarions_including_failing rename to internal/formatters/formatter-tests/junit/some_scenarions_including_failing diff --git a/formatter-tests/junit/two_scenarios_with_background_fail b/internal/formatters/formatter-tests/junit/two_scenarios_with_background_fail similarity index 100% rename from formatter-tests/junit/two_scenarios_with_background_fail rename to internal/formatters/formatter-tests/junit/two_scenarios_with_background_fail diff --git a/formatter-tests/junit/with_few_empty_scenarios b/internal/formatters/formatter-tests/junit/with_few_empty_scenarios similarity index 100% rename from formatter-tests/junit/with_few_empty_scenarios rename to internal/formatters/formatter-tests/junit/with_few_empty_scenarios diff --git a/formatter-tests/pretty/empty b/internal/formatters/formatter-tests/pretty/empty similarity index 100% rename from formatter-tests/pretty/empty rename to internal/formatters/formatter-tests/pretty/empty diff --git a/formatter-tests/pretty/empty_with_description b/internal/formatters/formatter-tests/pretty/empty_with_description similarity index 100% rename from formatter-tests/pretty/empty_with_description rename to internal/formatters/formatter-tests/pretty/empty_with_description diff --git a/formatter-tests/pretty/empty_with_single_scenario_without_steps b/internal/formatters/formatter-tests/pretty/empty_with_single_scenario_without_steps similarity index 100% rename from formatter-tests/pretty/empty_with_single_scenario_without_steps rename to internal/formatters/formatter-tests/pretty/empty_with_single_scenario_without_steps diff --git a/formatter-tests/pretty/empty_with_single_scenario_without_steps_and_description b/internal/formatters/formatter-tests/pretty/empty_with_single_scenario_without_steps_and_description similarity index 100% rename from formatter-tests/pretty/empty_with_single_scenario_without_steps_and_description rename to internal/formatters/formatter-tests/pretty/empty_with_single_scenario_without_steps_and_description diff --git a/formatter-tests/pretty/scenario_outline b/internal/formatters/formatter-tests/pretty/scenario_outline similarity index 92% rename from formatter-tests/pretty/scenario_outline rename to internal/formatters/formatter-tests/pretty/scenario_outline index 372412e..aa50d28 100644 --- a/formatter-tests/pretty/scenario_outline +++ b/internal/formatters/formatter-tests/pretty/scenario_outline @@ -1,9 +1,9 @@ Feature: outline Scenario Outline: outline # formatter-tests/features/scenario_outline.feature:5 - Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef - When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef - Then odd and even number # fmt_output_test.go:103 -> github.com/cucumber/godog_test.oddEvenStepDef + Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + Then odd and even number # fmt_output_test.go:103 -> github.com/cucumber/godog/internal/formatters_test.oddEvenStepDef Examples: tagged | odd | even | diff --git a/formatter-tests/pretty/scenario_with_background b/internal/formatters/formatter-tests/pretty/scenario_with_background similarity index 57% rename from formatter-tests/pretty/scenario_with_background rename to internal/formatters/formatter-tests/pretty/scenario_with_background index 22c75e3..c31b9a6 100644 --- a/formatter-tests/pretty/scenario_with_background +++ b/internal/formatters/formatter-tests/pretty/scenario_with_background @@ -1,12 +1,12 @@ Feature: single scenario with background Background: named - Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef - And passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + And passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef Scenario: scenario # formatter-tests/features/scenario_with_background.feature:7 - When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef - Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef 1 scenarios (1 passed) 4 steps (4 passed) diff --git a/formatter-tests/pretty/scenario_without_steps_with_background b/internal/formatters/formatter-tests/pretty/scenario_without_steps_with_background similarity index 100% rename from formatter-tests/pretty/scenario_without_steps_with_background rename to internal/formatters/formatter-tests/pretty/scenario_without_steps_with_background diff --git a/formatter-tests/pretty/single_scenario_with_passing_step b/internal/formatters/formatter-tests/pretty/single_scenario_with_passing_step similarity index 77% rename from formatter-tests/pretty/single_scenario_with_passing_step rename to internal/formatters/formatter-tests/pretty/single_scenario_with_passing_step index d24086f..537a239 100644 --- a/formatter-tests/pretty/single_scenario_with_passing_step +++ b/internal/formatters/formatter-tests/pretty/single_scenario_with_passing_step @@ -4,7 +4,7 @@ feature Scenario: one step passing # formatter-tests/features/single_scenario_with_passing_step.feature:6 - Given a passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + Given a passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef 1 scenarios (1 passed) 1 steps (1 passed) diff --git a/formatter-tests/pretty/some_scenarions_including_failing b/internal/formatters/formatter-tests/pretty/some_scenarions_including_failing similarity index 74% rename from formatter-tests/pretty/some_scenarions_including_failing rename to internal/formatters/formatter-tests/pretty/some_scenarions_including_failing index 689e4d4..cbd7f83 100644 --- a/formatter-tests/pretty/some_scenarions_including_failing +++ b/internal/formatters/formatter-tests/pretty/some_scenarions_including_failing @@ -1,19 +1,19 @@ Feature: some scenarios Scenario: failing # formatter-tests/features/some_scenarions_including_failing.feature:3 - Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef - When failing step # fmt_output_test.go:117 -> github.com/cucumber/godog_test.failingStepDef + Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + When failing step # fmt_output_test.go:117 -> github.com/cucumber/godog/internal/formatters_test.failingStepDef step failed - Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef Scenario: pending # formatter-tests/features/some_scenarions_including_failing.feature:8 - When pending step # fmt_output_test.go:115 -> github.com/cucumber/godog_test.pendingStepDef + When pending step # fmt_output_test.go:115 -> github.com/cucumber/godog/internal/formatters_test.pendingStepDef TODO: write pending definition - Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef Scenario: undefined # formatter-tests/features/some_scenarions_including_failing.feature:12 When undefined - Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef --- Failed steps: diff --git a/formatter-tests/pretty/two_scenarios_with_background_fail b/internal/formatters/formatter-tests/pretty/two_scenarios_with_background_fail similarity index 74% rename from formatter-tests/pretty/two_scenarios_with_background_fail rename to internal/formatters/formatter-tests/pretty/two_scenarios_with_background_fail index 643c910..a41f2c3 100644 --- a/formatter-tests/pretty/two_scenarios_with_background_fail +++ b/internal/formatters/formatter-tests/pretty/two_scenarios_with_background_fail @@ -1,16 +1,16 @@ Feature: two scenarios with background fail Background: - Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef - And failing step # fmt_output_test.go:117 -> github.com/cucumber/godog_test.failingStepDef + Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + And failing step # fmt_output_test.go:117 -> github.com/cucumber/godog/internal/formatters_test.failingStepDef step failed Scenario: one # formatter-tests/features/two_scenarios_with_background_fail.feature:7 - When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef - Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef Scenario: two # formatter-tests/features/two_scenarios_with_background_fail.feature:11 - Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef --- Failed steps: diff --git a/formatter-tests/pretty/with_few_empty_scenarios b/internal/formatters/formatter-tests/pretty/with_few_empty_scenarios similarity index 100% rename from formatter-tests/pretty/with_few_empty_scenarios rename to internal/formatters/formatter-tests/pretty/with_few_empty_scenarios diff --git a/formatter-tests/progress/empty b/internal/formatters/formatter-tests/progress/empty similarity index 100% rename from formatter-tests/progress/empty rename to internal/formatters/formatter-tests/progress/empty diff --git a/formatter-tests/progress/empty_with_description b/internal/formatters/formatter-tests/progress/empty_with_description similarity index 100% rename from formatter-tests/progress/empty_with_description rename to internal/formatters/formatter-tests/progress/empty_with_description diff --git a/formatter-tests/progress/empty_with_single_scenario_without_steps b/internal/formatters/formatter-tests/progress/empty_with_single_scenario_without_steps similarity index 100% rename from formatter-tests/progress/empty_with_single_scenario_without_steps rename to internal/formatters/formatter-tests/progress/empty_with_single_scenario_without_steps diff --git a/formatter-tests/progress/empty_with_single_scenario_without_steps_and_description b/internal/formatters/formatter-tests/progress/empty_with_single_scenario_without_steps_and_description similarity index 100% rename from formatter-tests/progress/empty_with_single_scenario_without_steps_and_description rename to internal/formatters/formatter-tests/progress/empty_with_single_scenario_without_steps_and_description diff --git a/formatter-tests/progress/scenario_outline b/internal/formatters/formatter-tests/progress/scenario_outline similarity index 100% rename from formatter-tests/progress/scenario_outline rename to internal/formatters/formatter-tests/progress/scenario_outline diff --git a/formatter-tests/progress/scenario_with_background b/internal/formatters/formatter-tests/progress/scenario_with_background similarity index 100% rename from formatter-tests/progress/scenario_with_background rename to internal/formatters/formatter-tests/progress/scenario_with_background diff --git a/formatter-tests/progress/scenario_without_steps_with_background b/internal/formatters/formatter-tests/progress/scenario_without_steps_with_background similarity index 100% rename from formatter-tests/progress/scenario_without_steps_with_background rename to internal/formatters/formatter-tests/progress/scenario_without_steps_with_background diff --git a/formatter-tests/progress/single_scenario_with_passing_step b/internal/formatters/formatter-tests/progress/single_scenario_with_passing_step similarity index 100% rename from formatter-tests/progress/single_scenario_with_passing_step rename to internal/formatters/formatter-tests/progress/single_scenario_with_passing_step diff --git a/formatter-tests/progress/some_scenarions_including_failing b/internal/formatters/formatter-tests/progress/some_scenarions_including_failing similarity index 100% rename from formatter-tests/progress/some_scenarions_including_failing rename to internal/formatters/formatter-tests/progress/some_scenarions_including_failing diff --git a/formatter-tests/progress/two_scenarios_with_background_fail b/internal/formatters/formatter-tests/progress/two_scenarios_with_background_fail similarity index 100% rename from formatter-tests/progress/two_scenarios_with_background_fail rename to internal/formatters/formatter-tests/progress/two_scenarios_with_background_fail diff --git a/formatter-tests/progress/with_few_empty_scenarios b/internal/formatters/formatter-tests/progress/with_few_empty_scenarios similarity index 100% rename from formatter-tests/progress/with_few_empty_scenarios rename to internal/formatters/formatter-tests/progress/with_few_empty_scenarios diff --git a/undefined_snippets_gen.go b/internal/formatters/undefined_snippets_gen.go similarity index 99% rename from undefined_snippets_gen.go rename to internal/formatters/undefined_snippets_gen.go index 456b50d..b61dfd3 100644 --- a/undefined_snippets_gen.go +++ b/internal/formatters/undefined_snippets_gen.go @@ -1,4 +1,4 @@ -package godog +package formatters import ( "fmt" diff --git a/internal/formatters/utils_test.go b/internal/formatters/utils_test.go new file mode 100644 index 0000000..6463141 --- /dev/null +++ b/internal/formatters/utils_test.go @@ -0,0 +1,24 @@ +package formatters + +import ( + "testing" + "time" + + "github.com/cucumber/godog/internal/utils" +) + +// this zeroes the time throughout whole test suite +// and makes it easier to assert output +// activated only when godog tests are being run +func init() { + utils.TimeNowFunc = func() time.Time { + return time.Time{} + } +} + +func TestTimeNowFunc(t *testing.T) { + now := utils.TimeNowFunc() + if !now.IsZero() { + t.Fatalf("expected zeroed time, but got: %s", now.Format(time.RFC3339)) + } +} diff --git a/feature.go b/internal/models/feature.go similarity index 69% rename from feature.go rename to internal/models/feature.go index 9dcebd1..bf1c77b 100644 --- a/feature.go +++ b/internal/models/feature.go @@ -1,22 +1,20 @@ -package godog +package models import ( "github.com/cucumber/messages-go/v10" ) -type feature struct { +// Feature is an internal object to group together +// the parsed gherkin document, the pickles and the +// raw content. +type Feature struct { *messages.GherkinDocument - pickles []*messages.Pickle - content []byte + Pickles []*messages.Pickle + Content []byte } -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] } - -func (f feature) findScenario(astScenarioID string) *messages.GherkinDocument_Feature_Scenario { +// FindScenario ... +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 @@ -26,7 +24,8 @@ func (f feature) findScenario(astScenarioID string) *messages.GherkinDocument_Fe return nil } -func (f feature) findBackground(astScenarioID string) *messages.GherkinDocument_Feature_Background { +// FindBackground ... +func (f Feature) FindBackground(astScenarioID string) *messages.GherkinDocument_Feature_Background { var bg *messages.GherkinDocument_Feature_Background for _, child := range f.GherkinDocument.Feature.Children { @@ -42,7 +41,8 @@ func (f feature) findBackground(astScenarioID string) *messages.GherkinDocument_ return nil } -func (f feature) findExample(exampleAstID string) (*messages.GherkinDocument_Feature_Scenario_Examples, *messages.GherkinDocument_Feature_TableRow) { +// FindExample ... +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 { @@ -58,7 +58,8 @@ func (f feature) findExample(exampleAstID string) (*messages.GherkinDocument_Fea return nil, nil } -func (f feature) findStep(astStepID string) *messages.GherkinDocument_Feature_Step { +// FindStep ... +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() { diff --git a/internal/models/feature_test.go b/internal/models/feature_test.go new file mode 100644 index 0000000..89fac1e --- /dev/null +++ b/internal/models/feature_test.go @@ -0,0 +1,60 @@ +package models_test + +import ( + "testing" + + "github.com/cucumber/godog/internal/testutils" + "github.com/stretchr/testify/assert" +) + +func Test_Find(t *testing.T) { + ft := testutils.BuildTestFeature(t) + + t.Run("scenario", func(t *testing.T) { + sc := ft.FindScenario(ft.Pickles[0].AstNodeIds[0]) + assert.NotNilf(t, sc, "expected scenario to not be nil") + }) + + t.Run("background", func(t *testing.T) { + bg := ft.FindBackground(ft.Pickles[0].AstNodeIds[0]) + assert.NotNilf(t, bg, "expected background to not be nil") + }) + + t.Run("example", func(t *testing.T) { + example, row := ft.FindExample(ft.Pickles[1].AstNodeIds[1]) + assert.NotNilf(t, example, "expected example to not be nil") + assert.NotNilf(t, row, "expected table row to not be nil") + }) + + t.Run("step", func(t *testing.T) { + for _, ps := range ft.Pickles[0].Steps { + step := ft.FindStep(ps.AstNodeIds[0]) + assert.NotNilf(t, step, "expected step to not be nil") + } + }) +} + +func Test_NotFind(t *testing.T) { + ft := testutils.BuildTestFeature(t) + + t.Run("scenario", func(t *testing.T) { + sc := ft.FindScenario("-") + assert.Nilf(t, sc, "expected scenario to be nil") + }) + + t.Run("background", func(t *testing.T) { + bg := ft.FindBackground("-") + assert.Nilf(t, bg, "expected background to be nil") + }) + + t.Run("example", func(t *testing.T) { + example, row := ft.FindExample("-") + assert.Nilf(t, example, "expected example to be nil") + assert.Nilf(t, row, "expected table row to be nil") + }) + + t.Run("step", func(t *testing.T) { + step := ft.FindStep("-") + assert.Nilf(t, step, "expected step to be nil") + }) +} diff --git a/internal/models/results.go b/internal/models/results.go new file mode 100644 index 0000000..461a5b8 --- /dev/null +++ b/internal/models/results.go @@ -0,0 +1,84 @@ +package models + +import ( + "time" + + "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/utils" +) + +// TestRunStarted ... +type TestRunStarted struct { + StartedAt time.Time +} + +// PickleResult ... +type PickleResult struct { + PickleID string + StartedAt time.Time +} + +// PickleStepResult ... +type PickleStepResult struct { + Status StepResultStatus + FinishedAt time.Time + Err error + + PickleID string + PickleStepID string + + Def *StepDefinition +} + +// NewStepResult ... +func NewStepResult(pickleID, pickleStepID string, match *StepDefinition) PickleStepResult { + return PickleStepResult{FinishedAt: utils.TimeNowFunc(), PickleID: pickleID, PickleStepID: pickleStepID, Def: match} +} + +// StepResultStatus ... +type StepResultStatus int + +const ( + // Passed ... + Passed StepResultStatus = iota + // Failed ... + Failed + // Skipped ... + Skipped + // Undefined ... + Undefined + // Pending ... + Pending +) + +// Color ... +func (st StepResultStatus) Color() colors.ColorFunc { + switch st { + case Passed: + return colors.Green + case Failed: + return colors.Red + case Skipped: + return colors.Cyan + default: + return colors.Yellow + } +} + +// String ... +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" + } +} diff --git a/internal/models/results_test.go b/internal/models/results_test.go new file mode 100644 index 0000000..2c74c63 --- /dev/null +++ b/internal/models/results_test.go @@ -0,0 +1,34 @@ +package models_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/models" +) + +type stepResultStatusTestCase struct { + st models.StepResultStatus + str string + clr colors.ColorFunc +} + +var stepResultStatusTestCases = []stepResultStatusTestCase{ + {st: models.Passed, str: "passed", clr: colors.Green}, + {st: models.Failed, str: "failed", clr: colors.Red}, + {st: models.Skipped, str: "skipped", clr: colors.Cyan}, + {st: models.Undefined, str: "undefined", clr: colors.Yellow}, + {st: models.Pending, str: "pending", clr: colors.Yellow}, + {st: -1, str: "unknown", clr: colors.Yellow}, +} + +func Test_StepResultStatus(t *testing.T) { + for _, tc := range stepResultStatusTestCases { + t.Run(tc.str, func(t *testing.T) { + assert.Equal(t, tc.str, tc.st.String()) + assert.Equal(t, tc.clr(tc.str), tc.st.Color()(tc.str)) + }) + } +} diff --git a/stepdef.go b/internal/models/stepdef.go similarity index 71% rename from stepdef.go rename to internal/models/stepdef.go index 2f075c6..b78da05 100644 --- a/stepdef.go +++ b/internal/models/stepdef.go @@ -1,35 +1,15 @@ -package godog +package models import ( "fmt" - "os" - "path/filepath" "reflect" "regexp" - "runtime" "strconv" - "strings" "github.com/cucumber/messages-go/v10" ) -var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`) - -// Steps allows to nest steps -// instead of returning an error in step func -// it is possible to return combined steps: -// -// func multistep(name string) godog.Steps { -// return godog.Steps{ -// fmt.Sprintf(`an user named "%s"`, name), -// fmt.Sprintf(`user "%s" is authenticated`, name), -// } -// } -// -// These steps will be matched and executed in -// sequential order. The first one which fails -// will result in main step failure. -type Steps []string +var typeOfBytes = reflect.TypeOf([]byte(nil)) // StepDefinition is a registered step definition // contains a StepHandler and regexp which @@ -40,50 +20,21 @@ type Steps []string // when step is matched and is either failed // or successful type StepDefinition struct { - args []interface{} - hv reflect.Value - Expr *regexp.Regexp - Handler interface{} + Args []interface{} + HandlerValue reflect.Value + Expr *regexp.Regexp + Handler interface{} // multistep related - nested bool - undefined []string + Nested bool + Undefined []string } -func (sd *StepDefinition) definitionID() string { - ptr := sd.hv.Pointer() - f := runtime.FuncForPC(ptr) - file, line := f.FileLine(ptr) - dir := filepath.Dir(file) - - fn := strings.Replace(f.Name(), dir, "", -1) - var parts []string - for _, gr := range matchFuncDefRef.FindAllStringSubmatch(fn, -1) { - parts = append(parts, strings.Trim(gr[1], "_.")) - } - if len(parts) > 0 { - // case when suite is a structure with methods - fn = strings.Join(parts, ".") - } else { - // case when steps are just plain funcs - fn = strings.Trim(fn, "_.") - } - - if pkg := os.Getenv("GODOG_TESTED_PACKAGE"); len(pkg) > 0 { - fn = strings.Replace(fn, pkg, "", 1) - fn = strings.TrimLeft(fn, ".") - fn = strings.Replace(fn, "..", ".", -1) - } - - return fmt.Sprintf("%s:%d -> %s", filepath.Base(file), line, fn) -} - -// run a step with the matched arguments using -// reflect -func (sd *StepDefinition) run() interface{} { - typ := sd.hv.Type() - if len(sd.args) < typ.NumIn() { - return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.args)) +// Run a step with the matched arguments using reflect +func (sd *StepDefinition) Run() interface{} { + typ := sd.HandlerValue.Type() + if len(sd.Args) < typ.NumIn() { + return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.Args)) } var values []reflect.Value for i := 0; i < typ.NumIn(); i++ { @@ -166,7 +117,7 @@ func (sd *StepDefinition) run() interface{} { } values = append(values, reflect.ValueOf(float32(v))) case reflect.Ptr: - arg := sd.args[i] + arg := sd.Args[i] switch param.Elem().String() { case "messages.PickleStepArgument_PickleDocString": if v, ok := arg.(*messages.PickleStepArgument); ok { @@ -211,11 +162,11 @@ func (sd *StepDefinition) run() interface{} { } } - return sd.hv.Call(values)[0].Interface() + return sd.HandlerValue.Call(values)[0].Interface() } func (sd *StepDefinition) shouldBeString(idx int) (string, error) { - arg := sd.args[idx] + arg := sd.Args[idx] s, ok := arg.(string) if !ok { return "", fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to string`, idx, arg, arg) diff --git a/internal/models/stepdef_test.go b/internal/models/stepdef_test.go new file mode 100644 index 0000000..e989000 --- /dev/null +++ b/internal/models/stepdef_test.go @@ -0,0 +1,107 @@ +package models_test + +import ( + "reflect" + "strings" + "testing" + + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/messages-go/v10" +) + +func TestShouldSupportIntTypes(t *testing.T) { + fn := func(a int64, b int32, c int16, d int8) error { return nil } + + def := &models.StepDefinition{ + Handler: fn, + HandlerValue: reflect.ValueOf(fn), + } + + def.Args = []interface{}{"1", "1", "1", "1"} + if err := def.Run(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + def.Args = []interface{}{"1", "1", "1", strings.Repeat("1", 9)} + if err := def.Run(); err == nil { + t.Fatalf("expected convertion fail for int8, but got none") + } +} + +func TestShouldSupportFloatTypes(t *testing.T) { + fn := func(a float64, b float32) error { return nil } + + def := &models.StepDefinition{ + Handler: fn, + HandlerValue: reflect.ValueOf(fn), + } + + def.Args = []interface{}{"1.1", "1.09"} + if err := def.Run(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + def.Args = []interface{}{"1.08", strings.Repeat("1", 65) + ".67"} + if err := def.Run(); err == nil { + t.Fatalf("expected convertion fail for float32, but got none") + } +} + +func TestShouldNotSupportOtherPointerTypesThanGherkin(t *testing.T) { + fn1 := func(a *int) error { return nil } + fn2 := func(a *messages.PickleStepArgument_PickleDocString) error { return nil } + fn3 := func(a *messages.PickleStepArgument_PickleTable) error { return nil } + + def1 := &models.StepDefinition{Handler: fn1, HandlerValue: reflect.ValueOf(fn1), Args: []interface{}{(*int)(nil)}} + def2 := &models.StepDefinition{Handler: fn2, HandlerValue: reflect.ValueOf(fn2), Args: []interface{}{&messages.PickleStepArgument_PickleDocString{}}} + def3 := &models.StepDefinition{Handler: fn3, HandlerValue: reflect.ValueOf(fn3), Args: []interface{}{(*messages.PickleStepArgument_PickleTable)(nil)}} + + if err := def1.Run(); err == nil { + t.Fatalf("expected conversion error, but got none") + } + if err := def2.Run(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if err := def3.Run(); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestShouldSupportOnlyByteSlice(t *testing.T) { + fn1 := func(a []byte) error { return nil } + fn2 := func(a []string) error { return nil } + + def1 := &models.StepDefinition{Handler: fn1, HandlerValue: reflect.ValueOf(fn1), Args: []interface{}{"str"}} + def2 := &models.StepDefinition{Handler: fn2, HandlerValue: reflect.ValueOf(fn2), Args: []interface{}{[]string{}}} + + if err := def1.Run(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if err := def2.Run(); err == nil { + t.Fatalf("expected conversion error, but got none") + } +} + +func TestUnexpectedArguments(t *testing.T) { + fn := func(a, b int) error { return nil } + def := &models.StepDefinition{Handler: fn, HandlerValue: reflect.ValueOf(fn)} + + def.Args = []interface{}{"1"} + if err := def.Run(); err == nil { + t.Fatalf("expected an error due to wrong number of arguments, but got none") + } + + def.Args = []interface{}{"one", "two"} + if err := def.Run(); err == nil { + t.Fatalf("expected conversion error, but got none") + } + + // @TODO maybe we should support duration + // fn2 := func(err time.Duration) error { return nil } + // def = &models.StepDefinition{Handler: fn2, HandlerValue: reflect.ValueOf(fn2)} + + // def.Args = []interface{}{"1"} + // if err := def.Run(); err == nil { + // t.Fatalf("expected an error due to wrong argument type, but got none") + // } +} diff --git a/storage.go b/internal/storage/storage.go similarity index 59% rename from storage.go rename to internal/storage/storage.go index 216d192..b3a8157 100644 --- a/storage.go +++ b/internal/storage/storage.go @@ -1,4 +1,4 @@ -package godog +package storage import ( "fmt" @@ -6,6 +6,8 @@ import ( "github.com/cucumber/messages-go/v10" "github.com/hashicorp/go-memdb" + + "github.com/cucumber/godog/internal/models" ) const ( @@ -31,15 +33,17 @@ const ( tablePickleStepResultIndexStatus string = "status" ) -type storage struct { +// Storage is a thread safe in-mem storage +type Storage struct { db *memdb.MemDB - testRunStarted testRunStarted - lock *sync.Mutex + testRunStarted models.TestRunStarted + testRunStartedLock *sync.Mutex } -func newStorage() *storage { - // Create the DB schema +// NewStorage will create an in-mem storage that +// is used across concurrent runners and formatters +func NewStorage() *Storage { schema := memdb.DBSchema{ Tables: map[string]*memdb.TableSchema{ tableFeature: { @@ -115,10 +119,12 @@ func newStorage() *storage { panic(err) } - return &storage{db: db, lock: new(sync.Mutex)} + return &Storage{db: db, testRunStartedLock: new(sync.Mutex)} } -func (s *storage) mustInsertPickle(p *messages.Pickle) { +// MustInsertPickle will insert a pickle and it's steps, +// will panic on error. +func (s *Storage) MustInsertPickle(p *messages.Pickle) { txn := s.db.Txn(writeMode) if err := txn.Insert(tablePickle, p); err != nil { @@ -134,12 +140,14 @@ func (s *storage) mustInsertPickle(p *messages.Pickle) { txn.Commit() } -func (s *storage) mustGetPickle(id string) *messages.Pickle { +// MustGetPickle will retrieve a pickle by id and panic on error. +func (s *Storage) MustGetPickle(id string) *messages.Pickle { v := s.mustFirst(tablePickle, tablePickleIndexID, id) return v.(*messages.Pickle) } -func (s *storage) mustGetPickles(uri string) (ps []*messages.Pickle) { +// MustGetPickles will retrieve pickles by URI and panic on error. +func (s *Storage) MustGetPickles(uri string) (ps []*messages.Pickle) { it := s.mustGet(tablePickle, tablePickleIndexURI, uri) for v := it.Next(); v != nil; v = it.Next() { ps = append(ps, v.(*messages.Pickle)) @@ -148,89 +156,102 @@ func (s *storage) mustGetPickles(uri string) (ps []*messages.Pickle) { return } -func (s *storage) mustGetPickleStep(id string) *messages.Pickle_PickleStep { +// MustGetPickleStep will retrieve a pickle step and panic on error. +func (s *Storage) MustGetPickleStep(id string) *messages.Pickle_PickleStep { v := s.mustFirst(tablePickleStep, tablePickleStepIndexID, id) return v.(*messages.Pickle_PickleStep) } -func (s *storage) mustInsertTestRunStarted(trs testRunStarted) { - s.lock.Lock() - defer s.lock.Unlock() +// MustInsertTestRunStarted will set the test run started event and panic on error. +func (s *Storage) MustInsertTestRunStarted(trs models.TestRunStarted) { + s.testRunStartedLock.Lock() + defer s.testRunStartedLock.Unlock() s.testRunStarted = trs } -func (s *storage) mustGetTestRunStarted() testRunStarted { - s.lock.Lock() - defer s.lock.Unlock() +// MustGetTestRunStarted will retrieve the test run started event and panic on error. +func (s *Storage) MustGetTestRunStarted() models.TestRunStarted { + s.testRunStartedLock.Lock() + defer s.testRunStartedLock.Unlock() return s.testRunStarted } -func (s *storage) mustInsertPickleResult(pr pickleResult) { +// MustInsertPickleResult will instert a pickle result and panic on error. +func (s *Storage) MustInsertPickleResult(pr models.PickleResult) { s.mustInsert(tablePickleResult, pr) } -func (s *storage) mustInsertPickleStepResult(psr pickleStepResult) { +// MustInsertPickleStepResult will insert a pickle step result and panic on error. +func (s *Storage) MustInsertPickleStepResult(psr models.PickleStepResult) { s.mustInsert(tablePickleStepResult, psr) } -func (s *storage) mustGetPickleResult(id string) pickleResult { +// MustGetPickleResult will retrieve a pickle result by id and panic on error. +func (s *Storage) MustGetPickleResult(id string) models.PickleResult { v := s.mustFirst(tablePickleResult, tablePickleResultIndexPickleID, id) - return v.(pickleResult) + return v.(models.PickleResult) } -func (s *storage) mustGetPickleResults() (prs []pickleResult) { +// MustGetPickleResults will retrieve all pickle results and panic on error. +func (s *Storage) MustGetPickleResults() (prs []models.PickleResult) { it := s.mustGet(tablePickleResult, tablePickleResultIndexPickleID) for v := it.Next(); v != nil; v = it.Next() { - prs = append(prs, v.(pickleResult)) + prs = append(prs, v.(models.PickleResult)) } return prs } -func (s *storage) mustGetPickleStepResult(id string) pickleStepResult { +// MustGetPickleStepResult will retrieve a pickle strep result by id and panic on error. +func (s *Storage) MustGetPickleStepResult(id string) models.PickleStepResult { v := s.mustFirst(tablePickleStepResult, tablePickleStepResultIndexPickleStepID, id) - return v.(pickleStepResult) + return v.(models.PickleStepResult) } -func (s *storage) mustGetPickleStepResultsByPickleID(pickleID string) (psrs []pickleStepResult) { +// MustGetPickleStepResultsByPickleID will retrieve pickle strep results by pickle id and panic on error. +func (s *Storage) MustGetPickleStepResultsByPickleID(pickleID string) (psrs []models.PickleStepResult) { it := s.mustGet(tablePickleStepResult, tablePickleStepResultIndexPickleID, pickleID) for v := it.Next(); v != nil; v = it.Next() { - psrs = append(psrs, v.(pickleStepResult)) + psrs = append(psrs, v.(models.PickleStepResult)) } return psrs } -func (s *storage) mustGetPickleStepResultsByStatus(status stepResultStatus) (psrs []pickleStepResult) { +// MustGetPickleStepResultsByStatus will retrieve pickle strep results by status and panic on error. +func (s *Storage) MustGetPickleStepResultsByStatus(status models.StepResultStatus) (psrs []models.PickleStepResult) { it := s.mustGet(tablePickleStepResult, tablePickleStepResultIndexStatus, status) for v := it.Next(); v != nil; v = it.Next() { - psrs = append(psrs, v.(pickleStepResult)) + psrs = append(psrs, v.(models.PickleStepResult)) } return psrs } -func (s *storage) mustInsertFeature(f *feature) { +// MustInsertFeature will insert a feature and panic on error. +func (s *Storage) MustInsertFeature(f *models.Feature) { s.mustInsert(tableFeature, f) } -func (s *storage) mustGetFeature(uri string) *feature { +// MustGetFeature will retrieve a feature by URI and panic on error. +func (s *Storage) MustGetFeature(uri string) *models.Feature { v := s.mustFirst(tableFeature, tableFeatureIndexURI, uri) - return v.(*feature) + return v.(*models.Feature) } -func (s *storage) mustGetFeatures() (fs []*feature) { +// MustGetFeatures will retrieve all features by and panic on error. +func (s *Storage) MustGetFeatures() (fs []*models.Feature) { it := s.mustGet(tableFeature, tableFeatureIndexURI) for v := it.Next(); v != nil; v = it.Next() { - fs = append(fs, v.(*feature)) + fs = append(fs, v.(*models.Feature)) } return } -func (s *storage) mustInsert(table string, obj interface{}) { +func (s *Storage) mustInsert(table string, obj interface{}) { txn := s.db.Txn(writeMode) if err := txn.Insert(table, obj); err != nil { @@ -240,7 +261,7 @@ func (s *storage) mustInsert(table string, obj interface{}) { txn.Commit() } -func (s *storage) mustFirst(table, index string, args ...interface{}) interface{} { +func (s *Storage) mustFirst(table, index string, args ...interface{}) interface{} { txn := s.db.Txn(readMode) defer txn.Abort() @@ -255,7 +276,7 @@ func (s *storage) mustFirst(table, index string, args ...interface{}) interface{ return v } -func (s *storage) mustGet(table, index string, args ...interface{}) memdb.ResultIterator { +func (s *Storage) mustGet(table, index string, args ...interface{}) memdb.ResultIterator { txn := s.db.Txn(readMode) defer txn.Abort() diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go new file mode 100644 index 0000000..be50a7b --- /dev/null +++ b/internal/storage/storage_test.go @@ -0,0 +1,187 @@ +package storage_test + +import ( + "testing" + "time" + + "github.com/cucumber/messages-go/v10" + "github.com/stretchr/testify/assert" + + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" + "github.com/cucumber/godog/internal/testutils" +) + +func Test_MustGetPickle(t *testing.T) { + s := storage.NewStorage() + ft := testutils.BuildTestFeature(t) + + expected := ft.Pickles[0] + s.MustInsertPickle(expected) + + actual := s.MustGetPickle(expected.Id) + assert.Equal(t, expected, actual) +} + +func Test_MustGetPickles(t *testing.T) { + s := storage.NewStorage() + ft := testutils.BuildTestFeature(t) + + expected := ft.Pickles + for _, pickle := range expected { + s.MustInsertPickle(pickle) + } + + actual := s.MustGetPickles(ft.Uri) + assert.Equal(t, expected, actual) +} + +func Test_MustGetPickleStep(t *testing.T) { + s := storage.NewStorage() + ft := testutils.BuildTestFeature(t) + + for _, pickle := range ft.Pickles { + s.MustInsertPickle(pickle) + } + + for _, pickle := range ft.Pickles { + for _, expected := range pickle.Steps { + actual := s.MustGetPickleStep(expected.Id) + assert.Equal(t, expected, actual) + } + } +} + +func Test_MustGetTestRunStarted(t *testing.T) { + s := storage.NewStorage() + + expected := models.TestRunStarted{StartedAt: time.Now()} + s.MustInsertTestRunStarted(expected) + + actual := s.MustGetTestRunStarted() + assert.Equal(t, expected, actual) +} + +func Test_MustGetPickleResult(t *testing.T) { + s := storage.NewStorage() + + const pickleID = "1" + expected := models.PickleResult{PickleID: pickleID} + s.MustInsertPickleResult(expected) + + actual := s.MustGetPickleResult(pickleID) + assert.Equal(t, expected, actual) +} + +func Test_MustGetPickleResults(t *testing.T) { + s := storage.NewStorage() + + expected := []models.PickleResult{{PickleID: "1"}, {PickleID: "2"}} + for _, pr := range expected { + s.MustInsertPickleResult(pr) + } + + actual := s.MustGetPickleResults() + assert.Equal(t, expected, actual) +} + +func Test_MustGetPickleStepResult(t *testing.T) { + s := storage.NewStorage() + + const pickleID = "1" + const stepID = "2" + + expected := models.PickleStepResult{ + Status: models.Passed, + PickleID: pickleID, + PickleStepID: stepID, + } + s.MustInsertPickleStepResult(expected) + + actual := s.MustGetPickleStepResult(stepID) + assert.Equal(t, expected, actual) +} + +func Test_MustGetPickleStepResultsByPickleID(t *testing.T) { + s := storage.NewStorage() + + const pickleID = "p1" + + expected := []models.PickleStepResult{ + { + Status: models.Passed, + PickleID: pickleID, + PickleStepID: "s1", + }, + { + Status: models.Passed, + PickleID: pickleID, + PickleStepID: "s2", + }, + } + + for _, psr := range expected { + s.MustInsertPickleStepResult(psr) + } + + actual := s.MustGetPickleStepResultsByPickleID(pickleID) + assert.Equal(t, expected, actual) +} + +func Test_MustGetPickleStepResultsByStatus(t *testing.T) { + s := storage.NewStorage() + + const pickleID = "p1" + + expected := []models.PickleStepResult{ + { + Status: models.Passed, + PickleID: pickleID, + PickleStepID: "s1", + }, + } + + testdata := []models.PickleStepResult{ + expected[0], + { + Status: models.Failed, + PickleID: pickleID, + PickleStepID: "s2", + }, + } + + for _, psr := range testdata { + s.MustInsertPickleStepResult(psr) + } + + actual := s.MustGetPickleStepResultsByStatus(models.Passed) + assert.Equal(t, expected, actual) +} + +func Test_MustGetFeature(t *testing.T) { + s := storage.NewStorage() + + const uri = "" + + expected := &models.Feature{GherkinDocument: &messages.GherkinDocument{Uri: uri}} + s.MustInsertFeature(expected) + + actual := s.MustGetFeature(uri) + assert.Equal(t, expected, actual) +} + +func Test_MustGetFeatures(t *testing.T) { + s := storage.NewStorage() + + expected := []*models.Feature{ + {GherkinDocument: &messages.GherkinDocument{Uri: "uri1"}}, + {GherkinDocument: &messages.GherkinDocument{Uri: "uri2"}}, + } + + for _, f := range expected { + s.MustInsertFeature(f) + } + + actual := s.MustGetFeatures() + assert.Equal(t, expected, actual) +} diff --git a/internal/testutils/utils.go b/internal/testutils/utils.go new file mode 100644 index 0000000..8965596 --- /dev/null +++ b/internal/testutils/utils.go @@ -0,0 +1,60 @@ +package testutils + +import ( + "strings" + "testing" + + "github.com/cucumber/gherkin-go/v11" + "github.com/cucumber/messages-go/v10" + "github.com/stretchr/testify/require" + + "github.com/cucumber/godog/internal/models" +) + +// BuildTestFeature creates a feature for testing purpose. +// +// The created feature includes: +// - a background +// - one normal scenario with three steps +// - one outline scenario with one example and three steps +func BuildTestFeature(t *testing.T) models.Feature { + newIDFunc := (&messages.Incrementing{}).NewId + + gherkinDocument, err := gherkin.ParseGherkinDocument(strings.NewReader(featureContent), newIDFunc) + require.NoError(t, err) + + path := t.Name() + gherkinDocument.Uri = path + pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc) + + ft := models.Feature{GherkinDocument: gherkinDocument, Pickles: pickles, Content: []byte(featureContent)} + require.Len(t, ft.Pickles, 2) + + require.Len(t, ft.Pickles[0].AstNodeIds, 1) + require.Len(t, ft.Pickles[0].Steps, 3) + + require.Len(t, ft.Pickles[1].AstNodeIds, 2) + require.Len(t, ft.Pickles[1].Steps, 3) + + return ft +} + +const featureContent = `Feature: eat godogs +In order to be happy +As a hungry gopher +I need to be able to eat godogs + +Background: + Given there are godogs + +Scenario: Eat 5 out of 12 + When I eat 5 + Then there should be 7 remaining + +Scenario Outline: Eat out of + When I eat + Then there should be remaining + + Examples: + | begin | dec | remain | + | 12 | 5 | 7 |` diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..f1ec21f --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,21 @@ +package utils + +import ( + "strings" + "time" +) + +// S repeats a space n times +func S(n int) string { + if n < 0 { + n = 1 + } + return strings.Repeat(" ", n) +} + +// TimeNowFunc is a utility function to simply testing +// by allowing TimeNowFunc to be defined to zero time +// to remove the time domain from tests +var TimeNowFunc = func() time.Time { + return time.Now() +} diff --git a/parser.go b/parser.go index 2824e35..3ff82e2 100644 --- a/parser.go +++ b/parser.go @@ -13,6 +13,7 @@ import ( "github.com/cucumber/gherkin-go/v11" "github.com/cucumber/messages-go/v10" + "github.com/cucumber/godog/internal/models" "github.com/cucumber/godog/internal/tags" ) @@ -30,7 +31,7 @@ func extractFeaturePathLine(p string) (string, int) { return retPath, line } -func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) { +func parseFeatureFile(path string, newIDFunc func() string) (*models.Feature, error) { reader, err := os.Open(path) if err != nil { return nil, err @@ -47,12 +48,12 @@ func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) { gherkinDocument.Uri = path pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc) - f := feature{GherkinDocument: gherkinDocument, pickles: pickles, content: buf.Bytes()} + f := models.Feature{GherkinDocument: gherkinDocument, Pickles: pickles, Content: buf.Bytes()} return &f, nil } -func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) { - var features []*feature +func parseFeatureDir(dir string, newIDFunc func() string) ([]*models.Feature, error) { + var features []*models.Feature return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error { if err != nil { return err @@ -76,8 +77,8 @@ func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) { }) } -func parsePath(path string) ([]*feature, error) { - var features []*feature +func parsePath(path string) ([]*models.Feature, error) { + var features []*models.Feature path, line := extractFeaturePathLine(path) @@ -99,23 +100,23 @@ func parsePath(path string) ([]*feature, error) { // filter scenario by line number var pickles []*messages.Pickle - for _, pickle := range ft.pickles { - sc := ft.findScenario(pickle.AstNodeIds[0]) + 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 + ft.Pickles = pickles return append(features, ft), nil } -func parseFeatures(filter string, paths []string) ([]*feature, error) { +func parseFeatures(filter string, paths []string) ([]*models.Feature, error) { var order int featureIdxs := make(map[string]int) - uniqueFeatureURI := make(map[string]*feature) + uniqueFeatureURI := make(map[string]*models.Feature) for _, path := range paths { feats, err := parsePath(path) @@ -140,7 +141,7 @@ func parseFeatures(filter string, paths []string) ([]*feature, error) { } } - var features = make([]*feature, len(uniqueFeatureURI)) + var features = make([]*models.Feature, len(uniqueFeatureURI)) for uri, feature := range uniqueFeatureURI { idx := featureIdxs[uri] features[idx] = feature @@ -151,11 +152,11 @@ func parseFeatures(filter string, paths []string) ([]*feature, error) { return features, nil } -func filterFeatures(filter string, features []*feature) (result []*feature) { +func filterFeatures(filter string, features []*models.Feature) (result []*models.Feature) { for _, ft := range features { - ft.pickles = tags.ApplyTagFilter(filter, ft.pickles) + ft.Pickles = tags.ApplyTagFilter(filter, ft.Pickles) - if ft.Feature != nil && len(ft.pickles) > 0 { + if ft.Feature != nil && len(ft.Pickles) > 0 { result = append(result, ft) } } diff --git a/results.go b/results.go deleted file mode 100644 index 5424750..0000000 --- a/results.go +++ /dev/null @@ -1,81 +0,0 @@ -package godog - -import ( - "time" - - "github.com/cucumber/godog/colors" -) - -type testRunStarted struct { - StartedAt time.Time -} - -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" - } -} diff --git a/run.go b/run.go index adf4a34..5e73721 100644 --- a/run.go +++ b/run.go @@ -12,8 +12,12 @@ import ( "strings" "sync" - "github.com/cucumber/godog/colors" "github.com/cucumber/messages-go/v10" + + "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" + "github.com/cucumber/godog/internal/utils" ) const ( @@ -29,12 +33,12 @@ type runner struct { randomSeed int64 stopOnFailure, strict bool - features []*feature + features []*models.Feature testSuiteInitializer testSuiteInitializer scenarioInitializer scenarioInitializer - storage *storage + storage *storage.Storage fmt Formatter } @@ -42,7 +46,7 @@ func (r *runner) concurrent(rate int) (failed bool) { var copyLock sync.Mutex if fmt, ok := r.fmt.(storageFormatter); ok { - fmt.setStorage(r.storage) + fmt.SetStorage(r.storage) } testSuiteContext := TestSuiteContext{} @@ -50,8 +54,8 @@ func (r *runner) concurrent(rate int) (failed bool) { r.testSuiteInitializer(&testSuiteContext) } - testRunStarted := testRunStarted{StartedAt: timeNowFunc()} - r.storage.mustInsertTestRunStarted(testRunStarted) + testRunStarted := models.TestRunStarted{StartedAt: utils.TimeNowFunc()} + r.storage.MustInsertTestRunStarted(testRunStarted) r.fmt.TestRunStarted() // run before suite handlers @@ -61,15 +65,15 @@ func (r *runner) concurrent(rate int) (failed bool) { queue := make(chan int, rate) for _, ft := range r.features { - pickles := make([]*messages.Pickle, len(ft.pickles)) + pickles := make([]*messages.Pickle, len(ft.Pickles)) if r.randomSeed != 0 { r := rand.New(rand.NewSource(r.randomSeed)) - perm := r.Perm(len(ft.pickles)) + perm := r.Perm(len(ft.Pickles)) for i, v := range perm { - pickles[v] = ft.pickles[i] + pickles[v] = ft.Pickles[i] } } else { - copy(pickles, ft.pickles) + copy(pickles, ft.Pickles) } for i, p := range pickles { @@ -78,7 +82,7 @@ func (r *runner) concurrent(rate int) (failed bool) { queue <- i // reserve space in queue if i == 0 { - r.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.content) + r.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.Content) } go func(fail *bool, pickle *messages.Pickle) { @@ -181,12 +185,12 @@ func runWithOptions(suiteName string, runner runner, opt Options) int { return exitOptionError } - runner.storage = newStorage() + runner.storage = storage.NewStorage() for _, feat := range runner.features { - runner.storage.mustInsertFeature(feat) + runner.storage.MustInsertFeature(feat) - for _, pickle := range feat.pickles { - runner.storage.mustInsertPickle(pickle) + for _, pickle := range feat.Pickles { + runner.storage.MustInsertPickle(pickle) } } diff --git a/fmt_progress_test.go b/run_progress_test.go similarity index 71% rename from fmt_progress_test.go rename to run_progress_test.go index a2b281c..2095b80 100644 --- a/fmt_progress_test.go +++ b/run_progress_test.go @@ -11,6 +11,9 @@ import ( "github.com/stretchr/testify/require" "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/formatters" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" ) var basicGherkinFeature = ` @@ -28,31 +31,31 @@ func Test_ProgressFormatterWhenStepPanics(t *testing.T) { require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) var buf bytes.Buffer w := colors.Uncolored(&buf) r := runner{ - fmt: progressFunc("progress", w), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", w), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { ctx.Step(`^one$`, func() error { return nil }) ctx.Step(`^two$`, func() error { panic("omg") }) }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) require.True(t, failed) actual := buf.String() - assert.Contains(t, actual, "godog/fmt_progress_test.go:41") + assert.Contains(t, actual, "godog/run_progress_test.go:44") } func Test_ProgressFormatterWithPanicInMultistep(t *testing.T) { @@ -62,14 +65,14 @@ func Test_ProgressFormatterWithPanicInMultistep(t *testing.T) { require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) var buf bytes.Buffer w := colors.Uncolored(&buf) r := runner{ - fmt: progressFunc("progress", w), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", w), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { ctx.Step(`^sub1$`, func() error { return nil }) ctx.Step(`^sub-sub$`, func() error { return nil }) @@ -79,10 +82,10 @@ func Test_ProgressFormatterWithPanicInMultistep(t *testing.T) { }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) @@ -96,14 +99,14 @@ func Test_ProgressFormatterMultistepTemplates(t *testing.T) { require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) var buf bytes.Buffer w := colors.Uncolored(&buf) r := runner{ - fmt: progressFunc("progress", w), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", w), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { ctx.Step(`^sub-sub$`, func() error { return nil }) ctx.Step(`^substep$`, func() Steps { return Steps{"sub-sub", `unavailable "John" cost 5`, "one", "three"} }) @@ -112,10 +115,10 @@ func Test_ProgressFormatterMultistepTemplates(t *testing.T) { }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) @@ -172,24 +175,24 @@ Feature: basic require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) var buf bytes.Buffer w := colors.Uncolored(&buf) r := runner{ - fmt: progressFunc("progress", w), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", w), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { ctx.Step(`^one$`, func() error { return nil }) ctx.Step(`^two:$`, func(doc *messages.PickleStepArgument_PickleDocString) Steps { return Steps{"one"} }) }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) @@ -210,8 +213,8 @@ Feature: basic require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) var subStep = `three: """ @@ -221,8 +224,8 @@ Feature: basic var buf bytes.Buffer w := colors.Uncolored(&buf) r := runner{ - fmt: progressFunc("progress", w), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", w), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { ctx.Step(`^one$`, func() error { return nil }) ctx.Step(`^two$`, func() Steps { return Steps{subStep} }) @@ -230,10 +233,10 @@ Feature: basic }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) diff --git a/run_test.go b/run_test.go index 78a77be..94a6a80 100644 --- a/run_test.go +++ b/run_test.go @@ -17,6 +17,9 @@ import ( "github.com/stretchr/testify/require" "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/formatters" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" ) func okStep() error { @@ -70,22 +73,22 @@ func Test_FailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) { require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) r := runner{ - fmt: progressFunc("progress", ioutil.Discard), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", ioutil.Discard), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { ctx.Step(`^one$`, func() error { return nil }) ctx.Step(`^two$`, func() error { return ErrPending }) }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) @@ -103,22 +106,22 @@ func Test_FailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) { require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) r := runner{ - fmt: progressFunc("progress", ioutil.Discard), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", ioutil.Discard), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { ctx.Step(`^one$`, func() error { return nil }) // two - is undefined }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) @@ -136,22 +139,22 @@ func Test_ShouldFailOnError(t *testing.T) { require.NoError(t, err) gd.Uri = path - ft := feature{GherkinDocument: gd} - ft.pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + ft := models.Feature{GherkinDocument: gd} + ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) r := runner{ - fmt: progressFunc("progress", ioutil.Discard), - features: []*feature{&ft}, + fmt: formatters.ProgressFormatterFunc("progress", ioutil.Discard), + features: []*models.Feature{&ft}, scenarioInitializer: func(ctx *ScenarioContext) { - ctx.Step(`^one$`, func() error { return nil }) ctx.Step(`^two$`, func() error { return fmt.Errorf("error") }) + ctx.Step(`^one$`, func() error { return nil }) }, } - r.storage = newStorage() - r.storage.mustInsertFeature(&ft) - for _, pickle := range ft.pickles { - r.storage.mustInsertPickle(pickle) + r.storage = storage.NewStorage() + r.storage.MustInsertFeature(&ft) + for _, pickle := range ft.Pickles { + r.storage.MustInsertPickle(pickle) } failed := r.concurrent(1) @@ -284,7 +287,7 @@ func Test_RandomizeRun(t *testing.T) { const createRandomSeedFlag = -1 const noConcurrencyFlag = 1 const formatter = "pretty" - const featurePath = "formatter-tests/features/with_few_empty_scenarios.feature" + const featurePath = "internal/formatters/formatter-tests/features/with_few_empty_scenarios.feature" fmtOutputScenarioInitializer := func(ctx *ScenarioContext) { ctx.Step(`^(?:a )?failing step`, failingStepDef) @@ -367,7 +370,7 @@ func Test_FormatterConcurrencyRun(t *testing.T) { "cucumber", } - featurePaths := []string{"formatter-tests/features"} + featurePaths := []string{"internal/formatters/formatter-tests/features"} const concurrency = 100 const noRandomFlag = 0 diff --git a/stacktrace_test.go b/stacktrace_test.go index 7dcfbb4..e988d8e 100644 --- a/stacktrace_test.go +++ b/stacktrace_test.go @@ -24,7 +24,7 @@ func callstack3() *stack { return &st } -func TestStacktrace(t *testing.T) { +func Test_Stacktrace(t *testing.T) { err := &traceError{ msg: "err msg", stack: callstack1(), diff --git a/stepdef_test.go b/stepdef_test.go deleted file mode 100644 index a4f71a9..0000000 --- a/stepdef_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package godog - -import ( - "reflect" - "strings" - "testing" - - "github.com/cucumber/messages-go/v10" -) - -func TestShouldSupportIntTypes(t *testing.T) { - fn := func(a int64, b int32, c int16, d int8) error { return nil } - - def := &StepDefinition{ - Handler: fn, - hv: reflect.ValueOf(fn), - } - - def.args = []interface{}{"1", "1", "1", "1"} - if err := def.run(); err != nil { - t.Fatalf("unexpected error: %v", err) - } - - def.args = []interface{}{"1", "1", "1", strings.Repeat("1", 9)} - if err := def.run(); err == nil { - t.Fatalf("expected convertion fail for int8, but got none") - } -} - -func TestShouldSupportFloatTypes(t *testing.T) { - fn := func(a float64, b float32) error { return nil } - - def := &StepDefinition{ - Handler: fn, - hv: reflect.ValueOf(fn), - } - - def.args = []interface{}{"1.1", "1.09"} - if err := def.run(); err != nil { - t.Fatalf("unexpected error: %v", err) - } - - def.args = []interface{}{"1.08", strings.Repeat("1", 65) + ".67"} - if err := def.run(); err == nil { - t.Fatalf("expected convertion fail for float32, but got none") - } -} - -func TestShouldNotSupportOtherPointerTypesThanGherkin(t *testing.T) { - fn1 := func(a *int) error { return nil } - fn2 := func(a *messages.PickleStepArgument_PickleDocString) error { return nil } - fn3 := func(a *messages.PickleStepArgument_PickleTable) error { return nil } - - def1 := &StepDefinition{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{(*int)(nil)}} - def2 := &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{&messages.PickleStepArgument_PickleDocString{}}} - def3 := &StepDefinition{Handler: fn3, hv: reflect.ValueOf(fn3), args: []interface{}{(*messages.PickleStepArgument_PickleTable)(nil)}} - - if err := def1.run(); err == nil { - t.Fatalf("expected conversion error, but got none") - } - if err := def2.run(); err != nil { - t.Fatalf("unexpected error: %v", err) - } - if err := def3.run(); err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestShouldSupportOnlyByteSlice(t *testing.T) { - fn1 := func(a []byte) error { return nil } - fn2 := func(a []string) error { return nil } - - def1 := &StepDefinition{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{"str"}} - def2 := &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{[]string{}}} - - if err := def1.run(); err != nil { - t.Fatalf("unexpected error: %v", err) - } - if err := def2.run(); err == nil { - t.Fatalf("expected conversion error, but got none") - } -} - -func TestUnexpectedArguments(t *testing.T) { - fn := func(a, b int) error { return nil } - def := &StepDefinition{Handler: fn, hv: reflect.ValueOf(fn)} - - def.args = []interface{}{"1"} - if err := def.run(); err == nil { - t.Fatalf("expected an error due to wrong number of arguments, but got none") - } - - def.args = []interface{}{"one", "two"} - if err := def.run(); err == nil { - t.Fatalf("expected conversion error, but got none") - } - - // @TODO maybe we should support duration - // fn2 := func(err time.Duration) error { return nil } - // def = &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2)} - - // def.args = []interface{}{"1"} - // if err := def.run(); err == nil { - // t.Fatalf("expected an error due to wrong argument type, but got none") - // } -} diff --git a/suite.go b/suite.go index 7ce2f26..864f581 100644 --- a/suite.go +++ b/suite.go @@ -6,10 +6,13 @@ import ( "strings" "github.com/cucumber/messages-go/v10" + + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" + "github.com/cucumber/godog/internal/utils" ) var errorInterface = reflect.TypeOf((*error)(nil)).Elem() -var typeOfBytes = reflect.TypeOf([]byte(nil)) // ErrUndefined is returned in case if step definition was not found var ErrUndefined = fmt.Errorf("step is undefined") @@ -19,10 +22,10 @@ var ErrUndefined = fmt.Errorf("step is undefined") var ErrPending = fmt.Errorf("step implementation is pending") type suite struct { - steps []*StepDefinition + steps []*models.StepDefinition fmt Formatter - storage *storage + storage *storage.Storage failed bool randomSeed int64 @@ -36,10 +39,10 @@ type suite struct { afterScenarioHandlers []func(*Scenario, error) } -func (s *suite) matchStep(step *messages.Pickle_PickleStep) *StepDefinition { +func (s *suite) matchStep(step *messages.Pickle_PickleStep) *models.StepDefinition { def := s.matchStepText(step.Text) if def != nil && step.Argument != nil { - def.args = append(def.args, step.Argument) + def.Args = append(def.Args, step.Argument) } return def } @@ -70,23 +73,23 @@ func (s *suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleSte return } - sr := newStepResult(pickle.Id, step.Id, match) + sr := models.NewStepResult(pickle.Id, step.Id, match) switch err { case nil: - sr.Status = passed - s.storage.mustInsertPickleStepResult(sr) + sr.Status = models.Passed + s.storage.MustInsertPickleStepResult(sr) s.fmt.Passed(pickle, step, match) case ErrPending: - sr.Status = pending - s.storage.mustInsertPickleStepResult(sr) + sr.Status = models.Pending + s.storage.MustInsertPickleStepResult(sr) s.fmt.Pending(pickle, step, match) default: - sr.Status = failed - sr.err = err - s.storage.mustInsertPickleStepResult(sr) + sr.Status = models.Failed + sr.Err = err + s.storage.MustInsertPickleStepResult(sr) s.fmt.Failed(pickle, step, match, err) } @@ -101,34 +104,34 @@ func (s *suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleSte return err } else if len(undef) > 0 { if match != nil { - match = &StepDefinition{ - args: match.args, - hv: match.hv, - Expr: match.Expr, - Handler: match.Handler, - nested: match.nested, - undefined: undef, + match = &models.StepDefinition{ + Args: match.Args, + HandlerValue: match.HandlerValue, + Expr: match.Expr, + Handler: match.Handler, + Nested: match.Nested, + Undefined: undef, } } - sr := newStepResult(pickle.Id, step.Id, match) - sr.Status = undefined - s.storage.mustInsertPickleStepResult(sr) + sr := models.NewStepResult(pickle.Id, step.Id, match) + sr.Status = models.Undefined + s.storage.MustInsertPickleStepResult(sr) s.fmt.Undefined(pickle, step, match) return ErrUndefined } if prevStepErr != nil { - sr := newStepResult(pickle.Id, step.Id, match) - sr.Status = skipped - s.storage.mustInsertPickleStepResult(sr) + sr := models.NewStepResult(pickle.Id, step.Id, match) + sr.Status = models.Skipped + s.storage.MustInsertPickleStepResult(sr) s.fmt.Skipped(pickle, step, match) return nil } - err = s.maybeSubSteps(match.run()) + err = s.maybeSubSteps(match.Run()) return } @@ -139,15 +142,15 @@ func (s *suite) maybeUndefined(text string, arg interface{}) ([]string, error) { } var undefined []string - if !step.nested { + if !step.Nested { return undefined, nil } if arg != nil { - step.args = append(step.args, arg) + step.Args = append(step.Args, arg) } - for _, next := range step.run().(Steps) { + for _, next := range step.Run().(Steps) { lines := strings.Split(next, "\n") // @TODO: we cannot currently parse table or content body from nested steps if len(lines) > 1 { @@ -182,14 +185,14 @@ func (s *suite) maybeSubSteps(result interface{}) error { for _, text := range steps { if def := s.matchStepText(text); def == nil { return ErrUndefined - } else if err := s.maybeSubSteps(def.run()); err != nil { + } else if err := s.maybeSubSteps(def.Run()); err != nil { return fmt.Errorf("%s: %+v", text, err) } } return nil } -func (s *suite) matchStepText(text string) *StepDefinition { +func (s *suite) matchStepText(text string) *models.StepDefinition { for _, h := range s.steps { if m := h.Expr.FindStringSubmatch(text); len(m) > 0 { var args []interface{} @@ -199,12 +202,12 @@ func (s *suite) matchStepText(text string) *StepDefinition { // since we need to assign arguments // better to copy the step definition - return &StepDefinition{ - args: args, - hv: h.hv, - Expr: h.Expr, - Handler: h.Handler, - nested: h.nested, + return &models.StepDefinition{ + Args: args, + HandlerValue: h.HandlerValue, + Expr: h.Expr, + Handler: h.Handler, + Nested: h.Nested, } } } @@ -254,8 +257,8 @@ func isEmptyFeature(pickles []*messages.Pickle) bool { func (s *suite) runPickle(pickle *messages.Pickle) (err error) { if len(pickle.Steps) == 0 { - pr := pickleResult{PickleID: pickle.Id, StartedAt: timeNowFunc()} - s.storage.mustInsertPickleResult(pr) + pr := models.PickleResult{PickleID: pickle.Id, StartedAt: utils.TimeNowFunc()} + s.storage.MustInsertPickleResult(pr) s.fmt.Pickle(pickle) return ErrUndefined @@ -266,8 +269,8 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) { f(pickle) } - pr := pickleResult{PickleID: pickle.Id, StartedAt: timeNowFunc()} - s.storage.mustInsertPickleResult(pr) + pr := models.PickleResult{PickleID: pickle.Id, StartedAt: utils.TimeNowFunc()} + s.storage.MustInsertPickleResult(pr) s.fmt.Pickle(pickle) diff --git a/suite_context_test.go b/suite_context_test.go index f7952f8..83eba78 100644 --- a/suite_context_test.go +++ b/suite_context_test.go @@ -15,7 +15,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/cucumber/godog/colors" + "github.com/cucumber/godog/internal/formatters" + "github.com/cucumber/godog/internal/models" + "github.com/cucumber/godog/internal/storage" "github.com/cucumber/godog/internal/tags" + "github.com/cucumber/godog/internal/utils" ) // InitializeScenario provides steps for godog suite execution and @@ -142,7 +146,7 @@ type firedEvent struct { type godogFeaturesScenario struct { paths []string - features []*feature + features []*models.Feature testedSuite *suite testSuiteContext TestSuiteContext events []*firedEvent @@ -155,7 +159,7 @@ func (tc *godogFeaturesScenario) ResetBeforeEachScenario(*Scenario) { tc.out.Reset() tc.paths = []string{} - tc.features = []*feature{} + tc.features = []*models.Feature{} tc.testedSuite = &suite{} tc.testSuiteContext = TestSuiteContext{} @@ -170,7 +174,7 @@ func (tc *godogFeaturesScenario) iSetVariableInjectionTo(to string) error { } func (tc *godogFeaturesScenario) iRunFeatureSuiteWithTags(tags string) error { - return tc.iRunFeatureSuiteWithTagsAndFormatter(tags, baseFmtFunc) + return tc.iRunFeatureSuiteWithTagsAndFormatter(tags, formatters.BaseFormatterFunc) } func (tc *godogFeaturesScenario) iRunFeatureSuiteWithFormatter(name string) error { @@ -188,25 +192,25 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithTagsAndFormatter(filter str } for _, feat := range tc.features { - feat.pickles = tags.ApplyTagFilter(filter, feat.pickles) + feat.Pickles = tags.ApplyTagFilter(filter, feat.Pickles) } - tc.testedSuite.storage = newStorage() + tc.testedSuite.storage = storage.NewStorage() for _, feat := range tc.features { - tc.testedSuite.storage.mustInsertFeature(feat) + tc.testedSuite.storage.MustInsertFeature(feat) - for _, pickle := range feat.pickles { - tc.testedSuite.storage.mustInsertPickle(pickle) + for _, pickle := range feat.Pickles { + tc.testedSuite.storage.MustInsertPickle(pickle) } } tc.testedSuite.fmt = fmtFunc("godog", colors.Uncolored(&tc.out)) if fmt, ok := tc.testedSuite.fmt.(storageFormatter); ok { - fmt.setStorage(tc.testedSuite.storage) + fmt.SetStorage(tc.testedSuite.storage) } - testRunStarted := testRunStarted{StartedAt: timeNowFunc()} - tc.testedSuite.storage.mustInsertTestRunStarted(testRunStarted) + testRunStarted := models.TestRunStarted{StartedAt: utils.TimeNowFunc()} + tc.testedSuite.storage.MustInsertTestRunStarted(testRunStarted) tc.testedSuite.fmt.TestRunStarted() for _, f := range tc.testSuiteContext.beforeSuiteHandlers { @@ -214,9 +218,9 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithTagsAndFormatter(filter str } for _, ft := range tc.features { - tc.testedSuite.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.content) + tc.testedSuite.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.Content) - for _, pickle := range ft.pickles { + for _, pickle := range ft.Pickles { if tc.testedSuite.stopOnFailure && tc.testedSuite.failed { continue } @@ -278,16 +282,16 @@ func (tc *godogFeaturesScenario) cleanupSnippet(snip string) string { } func (tc *godogFeaturesScenario) theUndefinedStepSnippetsShouldBe(body *DocString) error { - f, ok := tc.testedSuite.fmt.(*basefmt) + f, ok := tc.testedSuite.fmt.(*formatters.Basefmt) if !ok { - return fmt.Errorf("this step requires *basefmt, but there is: %T", tc.testedSuite.fmt) + return fmt.Errorf("this step requires *formatters.Basefmt, but there is: %T", tc.testedSuite.fmt) } - actual := tc.cleanupSnippet(f.snippets()) + actual := tc.cleanupSnippet(f.Snippets()) expected := tc.cleanupSnippet(body.Content) if actual != expected { - return fmt.Errorf("snippets do not match actual: %s", f.snippets()) + return fmt.Errorf("snippets do not match actual: %s", f.Snippets()) } return nil @@ -297,35 +301,32 @@ func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps * var expected = strings.Split(steps.Content, "\n") var actual, unmatched, matched []string - f, ok := tc.testedSuite.fmt.(*basefmt) - if !ok { - return fmt.Errorf("this step requires *basefmt, but there is: %T", tc.testedSuite.fmt) - } + storage := tc.testedSuite.storage switch status { case "passed": - for _, st := range f.storage.mustGetPickleStepResultsByStatus(passed) { - pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) + for _, st := range storage.MustGetPickleStepResultsByStatus(models.Passed) { + pickleStep := storage.MustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "failed": - for _, st := range f.storage.mustGetPickleStepResultsByStatus(failed) { - pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) + for _, st := range storage.MustGetPickleStepResultsByStatus(models.Failed) { + pickleStep := storage.MustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "skipped": - for _, st := range f.storage.mustGetPickleStepResultsByStatus(skipped) { - pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) + for _, st := range storage.MustGetPickleStepResultsByStatus(models.Skipped) { + pickleStep := storage.MustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "undefined": - for _, st := range f.storage.mustGetPickleStepResultsByStatus(undefined) { - pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) + for _, st := range storage.MustGetPickleStepResultsByStatus(models.Undefined) { + pickleStep := storage.MustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "pending": - for _, st := range f.storage.mustGetPickleStepResultsByStatus(pending) { - pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) + for _, st := range storage.MustGetPickleStepResultsByStatus(models.Pending) { + pickleStep := storage.MustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } default: @@ -406,7 +407,7 @@ func (tc *godogFeaturesScenario) aFeatureFile(path string, body *DocString) erro gd.Uri = path pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) - tc.features = append(tc.features, &feature{GherkinDocument: gd, pickles: pickles}) + tc.features = append(tc.features, &models.Feature{GherkinDocument: gd, Pickles: pickles}) return err } @@ -486,7 +487,7 @@ func (tc *godogFeaturesScenario) iRunFeatureSuite() error { func (tc *godogFeaturesScenario) numScenariosRegistered(expected int) (err error) { var num int for _, ft := range tc.features { - num += len(ft.pickles) + num += len(ft.Pickles) } if num != expected { @@ -571,12 +572,12 @@ func (tc *godogFeaturesScenario) theRenderJSONWillBe(docstring *DocString) error actualString := tc.out.String() actualString = actualSuiteCtxReg.ReplaceAllString(actualString, `suite_context_test.go:0`) - var expected []cukeFeatureJSON + var expected []formatters.CukeFeatureJSON if err := json.Unmarshal([]byte(expectedString), &expected); err != nil { return err } - var actual []cukeFeatureJSON + var actual []formatters.CukeFeatureJSON if err := json.Unmarshal([]byte(actualString), &actual); err != nil { return err } @@ -614,12 +615,12 @@ func (tc *godogFeaturesScenario) theRenderXMLWillBe(docstring *DocString) error expectedString := docstring.Content actualString := tc.out.String() - var expected junitPackageSuite + var expected formatters.JunitPackageSuite if err := xml.Unmarshal([]byte(expectedString), &expected); err != nil { return err } - var actual junitPackageSuite + var actual formatters.JunitPackageSuite if err := xml.Unmarshal([]byte(actualString), &actual); err != nil { return err } @@ -642,3 +643,11 @@ type asserter struct { func (a *asserter) Errorf(format string, args ...interface{}) { a.err = fmt.Errorf(format, args...) } + +func trimAllLines(s string) string { + var lines []string + for _, ln := range strings.Split(strings.TrimSpace(s), "\n") { + lines = append(lines, strings.TrimSpace(ln)) + } + return strings.Join(lines, "\n") +} diff --git a/test_context.go b/test_context.go index c209d1b..03554c4 100644 --- a/test_context.go +++ b/test_context.go @@ -7,6 +7,8 @@ import ( "github.com/cucumber/godog/internal/builder" "github.com/cucumber/messages-go/v10" + + "github.com/cucumber/godog/internal/models" ) // Scenario represents the executed scenario @@ -15,6 +17,32 @@ type Scenario = messages.Pickle // Step represents the executed step type Step = messages.Pickle_PickleStep +// Steps allows to nest steps +// instead of returning an error in step func +// it is possible to return combined steps: +// +// func multistep(name string) godog.Steps { +// return godog.Steps{ +// fmt.Sprintf(`an user named "%s"`, name), +// fmt.Sprintf(`user "%s" is authenticated`, name), +// } +// } +// +// These steps will be matched and executed in +// sequential order. The first one which fails +// will result in main step failure. +type Steps []string + +// StepDefinition is a registered step definition +// contains a StepHandler and regexp which +// is used to match a step. Args which +// were matched by last executed step +// +// This structure is passed to the formatter +// when step is matched and is either failed +// or successful +type StepDefinition = models.StepDefinition + // DocString represents the DocString argument made to a step definition type DocString = messages.PickleStepArgument_PickleDocString @@ -152,9 +180,9 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) { } def := &StepDefinition{ - Handler: stepFunc, - Expr: regex, - hv: v, + Handler: stepFunc, + Expr: regex, + HandlerValue: v, } typ = typ.Out(0) @@ -167,7 +195,7 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) { if typ.Elem().Kind() != reflect.String { panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind())) } - def.nested = true + def.Nested = true default: panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind())) } diff --git a/utils.go b/utils.go deleted file mode 100644 index 584394b..0000000 --- a/utils.go +++ /dev/null @@ -1,60 +0,0 @@ -package godog - -import ( - "strconv" - "strings" - "time" - - "github.com/cucumber/godog/colors" - "github.com/cucumber/messages-go/v10" -) - -var ( - red = colors.Red - redb = colors.Bold(colors.Red) - green = colors.Green - blackb = colors.Bold(colors.Black) - yellow = colors.Yellow - cyan = colors.Cyan - cyanb = colors.Bold(colors.Cyan) - whiteb = colors.Bold(colors.White) -) - -// repeats a space n times -func s(n int) string { - if n < 0 { - n = 1 - } - return strings.Repeat(" ", n) -} - -var timeNowFunc = func() time.Time { - return time.Now() -} - -func trimAllLines(s string) string { - var lines []string - for _, ln := range strings.Split(strings.TrimSpace(s), "\n") { - lines = append(lines, strings.TrimSpace(ln)) - } - 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 -} diff --git a/utils_test.go b/utils_test.go index dda2d74..a8ebe13 100644 --- a/utils_test.go +++ b/utils_test.go @@ -3,19 +3,21 @@ package godog import ( "testing" "time" + + "github.com/cucumber/godog/internal/utils" ) // this zeroes the time throughout whole test suite // and makes it easier to assert output // activated only when godog tests are being run func init() { - timeNowFunc = func() time.Time { + utils.TimeNowFunc = func() time.Time { return time.Time{} } } func TestTimeNowFunc(t *testing.T) { - now := timeNowFunc() + now := utils.TimeNowFunc() if !now.IsZero() { t.Fatalf("expected zeroed time, but got: %s", now.Format(time.RFC3339)) }