diff --git a/feature.go b/feature.go index d6c26c21..9dcebd17 100644 --- a/feature.go +++ b/feature.go @@ -7,9 +7,7 @@ import ( type feature struct { *messages.GherkinDocument pickles []*messages.Pickle - content []byte - order int } type sortFeaturesByName []*feature @@ -18,12 +16,6 @@ func (s sortFeaturesByName) Len() int { return len(s) } func (s sortFeaturesByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name } func (s sortFeaturesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -type sortFeaturesByOrder []*feature - -func (s sortFeaturesByOrder) Len() int { return len(s) } -func (s sortFeaturesByOrder) Less(i, j int) bool { return s[i].order < s[j].order } -func (s sortFeaturesByOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - func (f feature) findScenario(astScenarioID string) *messages.GherkinDocument_Feature_Scenario { for _, child := range f.GherkinDocument.Feature.Children { if sc := child.GetScenario(); sc != nil && sc.Id == astScenarioID { diff --git a/fmt_base.go b/fmt_base.go index 7b81cd78..2d84d8dc 100644 --- a/fmt_base.go +++ b/fmt_base.go @@ -9,7 +9,6 @@ import ( "strconv" "strings" "sync" - "time" "unicode" "github.com/cucumber/messages-go/v10" @@ -20,7 +19,6 @@ import ( func newBaseFmt(suite string, out io.Writer) *basefmt { return &basefmt{ suiteName: suite, - startedAt: timeNowFunc(), indent: 2, out: out, lock: new(sync.Mutex), @@ -29,42 +27,24 @@ func newBaseFmt(suite string, out io.Writer) *basefmt { type basefmt struct { suiteName string - - out io.Writer - owner interface{} - indent int + out io.Writer + indent int storage *storage - - startedAt time.Time - - firstFeature *bool - lock *sync.Mutex + lock *sync.Mutex } func (f *basefmt) setStorage(st *storage) { - f.storage = st -} - -func (f *basefmt) TestRunStarted() { f.lock.Lock() defer f.lock.Unlock() - firstFeature := true - f.firstFeature = &firstFeature + f.storage = st } -func (f *basefmt) Pickle(p *messages.Pickle) {} - +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) Feature(ft *messages.GherkinDocument, p string, c []byte) { - f.lock.Lock() - defer f.lock.Unlock() - - *f.firstFeature = false -} - func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { } func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -145,7 +125,9 @@ func (f *basefmt) Summary() { scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc))) } scenarios = append(scenarios, parts...) - elapsed := timeNowFunc().Sub(f.startedAt) + + testRunStartedAt := f.storage.mustGetTestRunStarted().StartedAt + elapsed := timeNowFunc().Sub(testRunStartedAt) fmt.Fprintln(f.out, "") @@ -182,15 +164,6 @@ func (f *basefmt) Summary() { } } -func (f *basefmt) Sync(cf ConcurrentFormatter) { - if source, ok := cf.(*basefmt); ok { - f.lock = source.lock - f.firstFeature = source.firstFeature - } -} - -func (f *basefmt) Copy(cf ConcurrentFormatter) {} - func (f *basefmt) snippets() string { undefinedStepResults := f.storage.mustGetPickleStepResultsByStatus(undefined) if len(undefinedStepResults) == 0 { diff --git a/fmt_cucumber.go b/fmt_cucumber.go index 9ab16b0c..10c1b5a6 100644 --- a/fmt_cucumber.go +++ b/fmt_cucumber.go @@ -46,18 +46,6 @@ func (f *cukefmt) Summary() { fmt.Fprintf(f.out, "%s\n", string(dat)) } -func (f *cukefmt) Sync(cf ConcurrentFormatter) { - if source, ok := cf.(*cukefmt); ok { - f.basefmt.Sync(source.basefmt) - } -} - -func (f *cukefmt) Copy(cf ConcurrentFormatter) { - if source, ok := cf.(*cukefmt); ok { - f.basefmt.Copy(source.basefmt) - } -} - func (f *cukefmt) buildCukeFeatures(features []*feature) (res []cukeFeatureJSON) { sort.Sort(sortFeaturesByName(features)) diff --git a/fmt_events.go b/fmt_events.go index ff40dc3b..a129f782 100644 --- a/fmt_events.go +++ b/fmt_events.go @@ -136,18 +136,6 @@ func (f *events) Summary() { }) } -func (f *events) Sync(cf ConcurrentFormatter) { - if source, ok := cf.(*events); ok { - f.basefmt.Sync(source.basefmt) - } -} - -func (f *events) Copy(cf ConcurrentFormatter) { - if source, ok := cf.(*events); ok { - f.basefmt.Copy(source.basefmt) - } -} - func (f *events) step(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep) { feature := f.storage.mustGetFeature(pickle.Uri) pickleStepResult := f.storage.mustGetPickleStepResult(pickleStep.Id) diff --git a/fmt_junit.go b/fmt_junit.go index cd89f93b..ec38cb2c 100644 --- a/fmt_junit.go +++ b/fmt_junit.go @@ -37,18 +37,6 @@ func (f *junitFormatter) Summary() { } } -func (f *junitFormatter) Sync(cf ConcurrentFormatter) { - if source, ok := cf.(*junitFormatter); ok { - f.basefmt.Sync(source.basefmt) - } -} - -func (f *junitFormatter) Copy(cf ConcurrentFormatter) { - if source, ok := cf.(*junitFormatter); ok { - f.basefmt.Copy(source.basefmt) - } -} - func junitTimeDuration(from, to time.Time) string { return strconv.FormatFloat(to.Sub(from).Seconds(), 'f', -1, 64) } @@ -57,10 +45,12 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { features := f.storage.mustGetFeatures() sort.Sort(sortFeaturesByName(features)) + testRunStartedAt := f.storage.mustGetTestRunStarted().StartedAt + suite := junitPackageSuite{ Name: f.suiteName, TestSuites: make([]*junitTestSuite, len(features)), - Time: junitTimeDuration(f.startedAt, timeNowFunc()), + Time: junitTimeDuration(testRunStartedAt, timeNowFunc()), } for idx, feature := range features { @@ -77,8 +67,8 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { testcaseNames[pickle.Name] = testcaseNames[pickle.Name] + 1 } - firstPickleStartedAt := f.startedAt - lastPickleFinishedAt := f.startedAt + firstPickleStartedAt := testRunStartedAt + lastPickleFinishedAt := testRunStartedAt var outlineNo = make(map[string]int) for idx, pickle := range pickles { diff --git a/fmt_pretty.go b/fmt_pretty.go index 697a9052..534cce86 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -26,6 +26,17 @@ var outlinePlaceholderRegexp = regexp.MustCompile("<[^>]+>") // a built in default pretty formatter type pretty struct { *basefmt + firstFeature *bool +} + +func (f *pretty) TestRunStarted() { + f.basefmt.TestRunStarted() + + f.lock.Lock() + defer f.lock.Unlock() + + firstFeature := true + f.firstFeature = &firstFeature } func (f *pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) { @@ -33,6 +44,8 @@ func (f *pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) { if !*f.firstFeature { fmt.Fprintln(f.out, "") } + + *f.firstFeature = false f.lock.Unlock() f.basefmt.Feature(gd, p, c) @@ -101,18 +114,6 @@ func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.printStep(pickle, step) } -func (f *pretty) Sync(cf ConcurrentFormatter) { - if source, ok := cf.(*pretty); ok { - f.basefmt.Sync(source.basefmt) - } -} - -func (f *pretty) Copy(cf ConcurrentFormatter) { - if source, ok := cf.(*pretty); ok { - f.basefmt.Copy(source.basefmt) - } -} - func (f *pretty) printFeature(feature *messages.GherkinDocument_Feature) { fmt.Fprintln(f.out, keywordAndName(feature.Keyword, feature.Name)) if strings.TrimSpace(feature.Description) != "" { diff --git a/fmt_progress.go b/fmt_progress.go index f7f4ede6..78c9d8da 100644 --- a/fmt_progress.go +++ b/fmt_progress.go @@ -144,16 +144,3 @@ func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_Pickle f.step(step.Id) } - -func (f *progress) Sync(cf ConcurrentFormatter) { - if source, ok := cf.(*progress); ok { - f.basefmt.Sync(source.basefmt) - f.steps = source.steps - } -} - -func (f *progress) Copy(cf ConcurrentFormatter) { - if source, ok := cf.(*progress); ok { - f.basefmt.Copy(source.basefmt) - } -} diff --git a/parser.go b/parser.go index 3c184019..ddb266b6 100644 --- a/parser.go +++ b/parser.go @@ -7,7 +7,6 @@ import ( "os" "path/filepath" "regexp" - "sort" "strconv" "strings" @@ -113,6 +112,7 @@ func parsePath(path string) ([]*feature, error) { func parseFeatures(filter string, paths []string) ([]*feature, error) { var order int + featureIdxs := make(map[string]int) uniqueFeatureURI := make(map[string]*feature) for _, path := range paths { feats, err := parsePath(path) @@ -131,27 +131,34 @@ func parseFeatures(filter string, paths []string) ([]*feature, error) { continue } - ft.order = order - order++ uniqueFeatureURI[ft.Uri] = ft + featureIdxs[ft.Uri] = order + + order++ } } - return filterFeatures(filter, uniqueFeatureURI), nil + var features = make([]*feature, len(uniqueFeatureURI)) + for uri, feature := range uniqueFeatureURI { + idx := featureIdxs[uri] + features[idx] = feature + } + + features = filterFeatures(filter, features) + + return features, nil } -func filterFeatures(tags string, collected map[string]*feature) (features []*feature) { - for _, ft := range collected { +func filterFeatures(tags string, features []*feature) (result []*feature) { + for _, ft := range features { ft.pickles = applyTagFilter(tags, ft.pickles) if ft.Feature != nil { - features = append(features, ft) + result = append(result, ft) } } - sort.Sort(sortFeaturesByOrder(features)) - - return features + return } func applyTagFilter(tags string, pickles []*messages.Pickle) (result []*messages.Pickle) { diff --git a/results.go b/results.go index 0f916921..54247508 100644 --- a/results.go +++ b/results.go @@ -6,6 +6,10 @@ import ( "github.com/cucumber/godog/colors" ) +type testRunStarted struct { + StartedAt time.Time +} + type pickleResult struct { PickleID string StartedAt time.Time diff --git a/run.go b/run.go index b41ec6d4..a9352284 100644 --- a/run.go +++ b/run.go @@ -55,6 +55,8 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool r.testSuiteInitializer(&testSuiteContext) } + testRunStarted := testRunStarted{StartedAt: timeNowFunc()} + r.storage.mustInsertTestRunStarted(testRunStarted) r.fmt.TestRunStarted() // run before suite handlers diff --git a/storage.go b/storage.go index d4d7e8a7..951fbcf6 100644 --- a/storage.go +++ b/storage.go @@ -2,6 +2,7 @@ package godog import ( "fmt" + "sync" "github.com/cucumber/messages-go/v10" "github.com/hashicorp/go-memdb" @@ -32,6 +33,9 @@ const ( type storage struct { db *memdb.MemDB + + testRunStarted testRunStarted + lock *sync.Mutex } func newStorage() *storage { @@ -112,7 +116,7 @@ func newStorage() *storage { panic(err) } - return &storage{db} + return &storage{db: db, lock: new(sync.Mutex)} } func (s *storage) mustInsertPickle(p *messages.Pickle) { @@ -150,6 +154,20 @@ func (s *storage) mustGetPickleStep(id string) *messages.Pickle_PickleStep { return v.(*messages.Pickle_PickleStep) } +func (s *storage) mustInsertTestRunStarted(trs testRunStarted) { + s.lock.Lock() + defer s.lock.Unlock() + + s.testRunStarted = trs +} + +func (s *storage) mustGetTestRunStarted() testRunStarted { + s.lock.Lock() + defer s.lock.Unlock() + + return s.testRunStarted +} + func (s *storage) mustInsertPickleResult(pr pickleResult) { s.mustInsert(tablePickleResult, pr) } diff --git a/suite_context.go b/suite_context.go index 3f15ad35..7d4c3747 100644 --- a/suite_context.go +++ b/suite_context.go @@ -195,6 +195,9 @@ func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error { fmt.setStorage(s.testedSuite.storage) s.testedSuite.fmt = fmt + testRunStarted := testRunStarted{StartedAt: timeNowFunc()} + s.testedSuite.storage.mustInsertTestRunStarted(testRunStarted) + s.testedSuite.fmt.TestRunStarted() s.testedSuite.run() s.testedSuite.fmt.Summary() @@ -226,6 +229,9 @@ func (s *suiteContext) iRunFeatureSuiteWithFormatter(name string) error { fmt.setStorage(s.testedSuite.storage) } + testRunStarted := testRunStarted{StartedAt: timeNowFunc()} + s.testedSuite.storage.mustInsertTestRunStarted(testRunStarted) + s.testedSuite.fmt.TestRunStarted() s.testedSuite.run() s.testedSuite.fmt.Summary() diff --git a/suite_context_test.go b/suite_context_test.go index 9353f538..82171fe3 100644 --- a/suite_context_test.go +++ b/suite_context_test.go @@ -157,6 +157,9 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithTags(tags string) error { fmt.setStorage(tc.testedSuite.storage) tc.testedSuite.fmt = fmt + testRunStarted := testRunStarted{StartedAt: timeNowFunc()} + tc.testedSuite.storage.mustInsertTestRunStarted(testRunStarted) + tc.testedSuite.fmt.TestRunStarted() tc.testedSuite.run() tc.testedSuite.fmt.Summary() @@ -188,6 +191,9 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithFormatter(name string) erro fmt.setStorage(tc.testedSuite.storage) } + testRunStarted := testRunStarted{StartedAt: timeNowFunc()} + tc.testedSuite.storage.mustInsertTestRunStarted(testRunStarted) + tc.testedSuite.fmt.TestRunStarted() tc.testedSuite.run() tc.testedSuite.fmt.Summary()