From cd7663fccf257ecc451afd31943c9a86600351eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20L=C3=B6nnblad?= Date: Tue, 9 Jun 2020 21:51:19 +0200 Subject: [PATCH] Added Pickle and PickleStep results to the in-mem storage --- fmt.go | 156 +++++++++++------------------------- fmt_cucumber.go | 42 ++++++---- fmt_events.go | 34 ++++---- fmt_junit.go | 46 ++++++++--- fmt_pretty.go | 90 ++++++++++++--------- fmt_progress.go | 29 ++++--- run.go | 19 +++-- storage.go | 179 ++++++++++++++++++++++++++++++++++++++++-- suite.go | 138 ++++++++++++++++++-------------- suite_context.go | 77 +++++------------- suite_context_test.go | 77 +++++------------- test_context_test.go | 5 ++ 12 files changed, 508 insertions(+), 384 deletions(-) diff --git a/fmt.go b/fmt.go index ae02fdd..1e28aa6 100644 --- a/fmt.go +++ b/fmt.go @@ -139,25 +139,25 @@ func (st stepResultStatus) String() string { } } -type stepResult struct { - status stepResultStatus - time time.Time - err error +type pickleStepResult struct { + Status stepResultStatus + finishedAt time.Time + err error - pickleID string - pickleStepID string + PickleID string + PickleStepID string def *StepDefinition } -func newStepResult(pickleID, pickleStepID string, match *StepDefinition) *stepResult { - return &stepResult{time: timeNowFunc(), pickleID: pickleID, pickleStepID: pickleStepID, def: match} +func newStepResult(pickleID, pickleStepID string, match *StepDefinition) pickleStepResult { + return pickleStepResult{finishedAt: timeNowFunc(), PickleID: pickleID, PickleStepID: pickleStepID, def: match} } func newBaseFmt(suite string, out io.Writer) *basefmt { return &basefmt{ suiteName: suite, - started: timeNowFunc(), + startedAt: timeNowFunc(), indent: 2, out: out, lock: new(sync.Mutex), @@ -173,8 +173,8 @@ type basefmt struct { storage *storage - started time.Time - features []*feature + startedAt time.Time + features []*feature firstFeature *bool lock *sync.Mutex @@ -188,10 +188,6 @@ func (f *basefmt) lastFeature() *feature { return f.features[len(f.features)-1] } -func (f *basefmt) lastStepResult() *stepResult { - return f.lastFeature().lastStepResult() -} - func (f *basefmt) findFeature(scenarioAstID string) *feature { for _, ft := range f.features { if sc := ft.findScenario(scenarioAstID); sc != nil { @@ -250,15 +246,7 @@ func (f *basefmt) TestRunStarted() { f.firstFeature = &firstFeature } -func (f *basefmt) Pickle(p *messages.Pickle) { - f.lock.Lock() - defer f.lock.Unlock() - - feature := f.features[len(f.features)-1] - - pr := pickleResult{pickleID: p.Id, time: timeNowFunc()} - feature.pickleResults = append(feature.pickleResults, &pr) -} +func (f *basefmt) Pickle(p *messages.Pickle) {} func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {} @@ -268,94 +256,60 @@ func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) { *f.firstFeature = false - f.features = append(f.features, &feature{path: p, GherkinDocument: ft, time: timeNowFunc()}) + f.features = append(f.features, &feature{GherkinDocument: ft, time: timeNowFunc()}) } func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.lock.Lock() - defer f.lock.Unlock() - - s := newStepResult(pickle.Id, step.Id, match) - s.status = passed - f.lastFeature().appendStepResult(s) } - func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.lock.Lock() - defer f.lock.Unlock() - - s := newStepResult(pickle.Id, step.Id, match) - s.status = skipped - f.lastFeature().appendStepResult(s) } - func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.lock.Lock() - defer f.lock.Unlock() - - s := newStepResult(pickle.Id, step.Id, match) - s.status = undefined - f.lastFeature().appendStepResult(s) } - func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { - f.lock.Lock() - defer f.lock.Unlock() - - s := newStepResult(pickle.Id, step.Id, match) - s.status = failed - s.err = err - f.lastFeature().appendStepResult(s) } - func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { - f.lock.Lock() - defer f.lock.Unlock() - - s := newStepResult(pickle.Id, step.Id, match) - s.status = pending - f.lastFeature().appendStepResult(s) } func (f *basefmt) Summary() { var totalSc, passedSc, undefinedSc int var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int - for _, feat := range f.features { - for _, pr := range feat.pickleResults { - var prStatus stepResultStatus - totalSc++ + pickleResults := f.storage.mustGetPickleResults() + for _, pr := range pickleResults { + var prStatus stepResultStatus + totalSc++ - if len(pr.stepResults) == 0 { + pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pr.PickleID) + + if len(pickleStepResults) == 0 { + prStatus = undefined + } + + for _, sr := range pickleStepResults { + totalSt++ + + switch sr.Status { + case passed: + prStatus = passed + passedSt++ + case failed: + prStatus = failed + failedSt++ + case skipped: + skippedSt++ + case undefined: prStatus = undefined + undefinedSt++ + case pending: + prStatus = pending + pendingSt++ } + } - for _, sr := range pr.stepResults { - totalSt++ - - switch sr.status { - case passed: - prStatus = passed - passedSt++ - case failed: - prStatus = failed - failedSt++ - case skipped: - skippedSt++ - case undefined: - prStatus = undefined - undefinedSt++ - case pending: - prStatus = pending - pendingSt++ - } - } - - if prStatus == passed { - passedSc++ - } else if prStatus == undefined { - undefinedSc++ - } + if prStatus == passed { + passedSc++ + } else if prStatus == undefined { + undefinedSc++ } } @@ -385,7 +339,7 @@ func (f *basefmt) Summary() { scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc))) } scenarios = append(scenarios, parts...) - elapsed := timeNowFunc().Sub(f.started) + elapsed := timeNowFunc().Sub(f.startedAt) fmt.Fprintln(f.out, "") @@ -437,22 +391,8 @@ func (f *basefmt) Copy(cf ConcurrentFormatter) { } } -func (f *basefmt) findStepResults(status stepResultStatus) (res []*stepResult) { - for _, feat := range f.features { - for _, pr := range feat.pickleResults { - for _, sr := range pr.stepResults { - if sr.status == status { - res = append(res, sr) - } - } - } - } - - return -} - func (f *basefmt) snippets() string { - undefinedStepResults := f.findStepResults(undefined) + undefinedStepResults := f.storage.mustGetPickleStepResultsByStatus(undefined) if len(undefinedStepResults) == 0 { return "" } @@ -461,7 +401,7 @@ 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 diff --git a/fmt_cucumber.go b/fmt_cucumber.go index e370a4c..3eb7802 100644 --- a/fmt_cucumber.go +++ b/fmt_cucumber.go @@ -17,6 +17,8 @@ import ( "io" "sort" "strings" + + "github.com/cucumber/messages-go/v10" ) func init() { @@ -61,7 +63,11 @@ func (f *cukefmt) buildCukeFeatures(features []*feature) (res []cukeFeatureJSON) for idx, feat := range features { cukeFeature := buildCukeFeature(feat) - cukeFeature.Elements = f.buildCukeElements(feat.pickleResults) + + pickles := f.storage.mustGetPickles(feat.Uri) + sort.Sort(sortPicklesByID(pickles)) + + cukeFeature.Elements = f.buildCukeElements(pickles) for jdx, elem := range cukeFeature.Elements { elem.ID = cukeFeature.ID + ";" + makeCukeID(elem.Name) + elem.ID @@ -75,26 +81,29 @@ func (f *cukefmt) buildCukeFeatures(features []*feature) (res []cukeFeatureJSON) return res } -func (f *cukefmt) buildCukeElements(pickleResults []*pickleResult) (res []cukeElement) { - res = make([]cukeElement, len(pickleResults)) +func (f *cukefmt) buildCukeElements(pickles []*messages.Pickle) (res []cukeElement) { + res = make([]cukeElement, len(pickles)) - for idx, pickleResult := range pickleResults { - pickle := f.storage.mustGetPickle(pickleResult.pickleID) + for idx, pickle := range pickles { + pickleResult := f.storage.mustGetPickleResult(pickle.Id) + pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) cukeElement := f.buildCukeElement(pickle.Name, pickle.AstNodeIds) - stepStartedAt := pickleResult.startedAt() + stepStartedAt := pickleResult.StartedAt - cukeElement.Steps = make([]cukeStep, len(pickleResult.stepResults)) - for jdx, stepResult := range pickleResult.stepResults { - cukeStep := f.buildCukeStep(stepResult) + cukeElement.Steps = make([]cukeStep, len(pickleStepResults)) + sort.Sort(sortPickleStepResultsByPickleStepID(pickleStepResults)) - stepResultFinishedAt := stepResult.time + for jdx, stepResult := range pickleStepResults { + cukeStep := f.buildCukeStep(pickle, stepResult) + + 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 } @@ -172,7 +181,7 @@ type cukeFeatureJSON struct { func buildCukeFeature(feat *feature) cukeFeatureJSON { cukeFeature := cukeFeatureJSON{ - URI: feat.path, + URI: feat.Uri, ID: makeCukeID(feat.Feature.Name), Keyword: feat.Feature.Keyword, Name: feat.Feature.Name, @@ -238,9 +247,8 @@ func (f *cukefmt) buildCukeElement(pickleName string, pickleAstNodeIDs []string) return cukeElement } -func (f *cukefmt) buildCukeStep(stepResult *stepResult) (cukeStep cukeStep) { - pickle := f.storage.mustGetPickle(stepResult.pickleID) - pickleStep := f.storage.mustGetPickleStep(stepResult.pickleStepID) +func (f *cukefmt) buildCukeStep(pickle *messages.Pickle, stepResult pickleStepResult) (cukeStep cukeStep) { + pickleStep := f.storage.mustGetPickleStep(stepResult.PickleStepID) step := f.findStep(pickleStep.AstNodeIds[0]) line := step.Location.Line @@ -277,12 +285,12 @@ func (f *cukefmt) buildCukeStep(stepResult *stepResult) (cukeStep cukeStep) { cukeStep.Match.Location = strings.Split(stepResult.def.definitionID(), " ")[0] } - cukeStep.Result.Status = stepResult.status.String() + cukeStep.Result.Status = stepResult.Status.String() if stepResult.err != nil { cukeStep.Result.Error = stepResult.err.Error() } - if stepResult.status == undefined || stepResult.status == pending { + if stepResult.Status == undefined || stepResult.Status == pending { cukeStep.Match.Location = fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line) } diff --git a/fmt_events.go b/fmt_events.go index ad104c3..b047175 100644 --- a/fmt_events.go +++ b/fmt_events.go @@ -103,10 +103,13 @@ func (f *events) Feature(ft *messages.GherkinDocument, p string, c []byte) { func (f *events) Summary() { // @TODO: determine status status := passed - if len(f.findStepResults(failed)) > 0 { + + f.storage.mustGetPickleStepResultsByStatus(failed) + + if len(f.storage.mustGetPickleStepResultsByStatus(failed)) > 0 { status = failed - } else if len(f.findStepResults(passed)) == 0 { - if len(f.findStepResults(undefined)) > len(f.findStepResults(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 @@ -145,13 +148,13 @@ func (f *events) Copy(cf ConcurrentFormatter) { } } -func (f *events) step(pickle *messages.Pickle, res *stepResult) { - pickleStep := f.storage.mustGetPickleStep(res.pickleStepID) +func (f *events) step(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep) { + pickleStepResult := f.storage.mustGetPickleStepResult(pickleStep.Id) step := f.findStep(pickleStep.AstNodeIds[0]) var errMsg string - if res.err != nil { - errMsg = res.err.Error() + if pickleStepResult.err != nil { + errMsg = pickleStepResult.err.Error() } f.event(&struct { Event string `json:"event"` @@ -163,15 +166,16 @@ func (f *events) step(pickle *messages.Pickle, res *stepResult) { "TestStepFinished", fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line), timeNowFunc().UnixNano() / nanoSec, - res.status.String(), + pickleStepResult.Status.String(), errMsg, }) if isLastStep(pickle, pickleStep) { var status string - for _, stepResult := range f.lastFeature().lastPickleResult().stepResults { - switch stepResult.status { + pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + for _, stepResult := range pickleStepResults { + switch stepResult.Status { case passed: status = passed.String() case failed: @@ -250,7 +254,7 @@ func (f *events) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.step(pickle, f.lastStepResult()) + f.step(pickle, step) } func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -259,7 +263,7 @@ func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.step(pickle, f.lastStepResult()) + f.step(pickle, step) } func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -268,7 +272,7 @@ func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pickle f.lock.Lock() defer f.lock.Unlock() - f.step(pickle, f.lastStepResult()) + f.step(pickle, step) } func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { @@ -277,7 +281,7 @@ func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.step(pickle, f.lastStepResult()) + f.step(pickle, step) } func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -286,7 +290,7 @@ func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.step(pickle, f.lastStepResult()) + f.step(pickle, step) } func (f *events) scenarioLocation(pickle *messages.Pickle) string { diff --git a/fmt_junit.go b/fmt_junit.go index 513b7c1..a6890ef 100644 --- a/fmt_junit.go +++ b/fmt_junit.go @@ -57,30 +57,53 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { suite := junitPackageSuite{ Name: f.suiteName, TestSuites: make([]*junitTestSuite, len(f.features)), - Time: junitTimeDuration(f.started, timeNowFunc()), + Time: junitTimeDuration(f.startedAt, timeNowFunc()), } sort.Sort(sortByName(f.features)) for idx, feat := range f.features { + pickles := f.storage.mustGetPickles(feat.Uri) + sort.Sort(sortPicklesByID(pickles)) + + var finishedAt = feat.startedAt() + + if len(pickles) > 0 { + lastPickle := pickles[len(pickles)-1] + + if len(lastPickle.Steps) > 0 { + lastStep := lastPickle.Steps[len(lastPickle.Steps)-1] + lastPickleStepResult := f.storage.mustGetPickleStepResult(lastStep.Id) + finishedAt = lastPickleStepResult.finishedAt + } + } + ts := junitTestSuite{ Name: feat.GherkinDocument.Feature.Name, - Time: junitTimeDuration(feat.startedAt(), feat.finishedAt()), - TestCases: make([]*junitTestCase, len(feat.pickleResults)), + Time: junitTimeDuration(feat.startedAt(), finishedAt), + TestCases: make([]*junitTestCase, len(pickles)), } var testcaseNames = make(map[string]int) - for _, pickleResult := range feat.pickleResults { - pickle := f.storage.mustGetPickle(pickleResult.pickleID) + for _, pickle := range pickles { testcaseNames[pickle.Name] = testcaseNames[pickle.Name] + 1 } var outlineNo = make(map[string]int) - for idx, pickleResult := range feat.pickleResults { + for idx, pickle := range pickles { tc := junitTestCase{} - tc.Time = junitTimeDuration(pickleResult.startedAt(), pickleResult.finishedAt()) - pickle := f.storage.mustGetPickle(pickleResult.pickleID) + pickleResult := f.storage.mustGetPickleResult(pickle.Id) + + var finishedAt = pickleResult.StartedAt + + if len(pickle.Steps) > 0 { + lastStep := pickle.Steps[len(pickle.Steps)-1] + lastPickleStepResult := f.storage.mustGetPickleStepResult(lastStep.Id) + finishedAt = lastPickleStepResult.finishedAt + } + + tc.Time = junitTimeDuration(pickleResult.StartedAt, finishedAt) tc.Name = pickle.Name if testcaseNames[tc.Name] > 1 { @@ -91,10 +114,11 @@ func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { ts.Tests++ suite.Tests++ - for _, stepResult := range pickleResult.stepResults { - pickleStep := f.storage.mustGetPickleStep(stepResult.pickleStepID) + pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + for _, stepResult := range pickleStepResults { + pickleStep := f.storage.mustGetPickleStep(stepResult.PickleStepID) - switch stepResult.status { + switch stepResult.Status { case passed: tc.Status = passed.String() case failed: diff --git a/fmt_pretty.go b/fmt_pretty.go index 3cee435..bc1435d 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "regexp" + "sort" "strings" "unicode/utf8" @@ -61,7 +62,7 @@ func (f *pretty) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.printStep(pickle, f.lastStepResult()) + f.printStep(pickle, step) } func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -70,7 +71,7 @@ func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.printStep(pickle, f.lastStepResult()) + f.printStep(pickle, step) } func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -79,7 +80,7 @@ func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pickle f.lock.Lock() defer f.lock.Unlock() - f.printStep(pickle, f.lastStepResult()) + f.printStep(pickle, step) } func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { @@ -88,7 +89,7 @@ func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.printStep(pickle, f.lastStepResult()) + f.printStep(pickle, step) } func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -97,7 +98,7 @@ func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.printStep(pickle, f.lastStepResult()) + f.printStep(pickle, step) } func (f *pretty) Sync(cf ConcurrentFormatter) { @@ -145,7 +146,7 @@ func (f *pretty) scenarioLengths(scenarioAstID string) (scenarioHeaderLength int func (f *pretty) printScenarioHeader(astScenario *messages.GherkinDocument_Feature_Scenario, spaceFilling int) { text := s(f.indent) + keywordAndName(astScenario.Keyword, astScenario.Name) - text += s(spaceFilling) + f.line(f.lastFeature().path, astScenario.Location) + text += s(spaceFilling) + f.line(f.lastFeature().Uri, astScenario.Location) fmt.Fprintln(f.out, "\n"+text) } @@ -192,12 +193,15 @@ func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) { // Summary sumarize the feature formatter output func (f *pretty) Summary() { - failedStepResults := f.findStepResults(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) + pickle := f.storage.mustGetPickle(fail.PickleID) + pickleStep := f.storage.mustGetPickleStep(fail.PickleStepID) feature := f.findFeature(pickle.AstNodeIds[0]) @@ -207,8 +211,8 @@ func (f *pretty) Summary() { astStep := f.findStep(pickleStep.AstNodeIds[0]) stepDesc := strings.TrimSpace(astStep.Keyword) + " " + pickleStep.Text - fmt.Fprintln(f.out, s(f.indent)+red(scenarioDesc)+f.line(feature.path, astScenario.Location)) - fmt.Fprintln(f.out, s(f.indent*2)+red(stepDesc)+f.line(feature.path, astStep.Location)) + fmt.Fprintln(f.out, s(f.indent)+red(scenarioDesc)+f.line(feature.Uri, astScenario.Location)) + fmt.Fprintln(f.out, s(f.indent*2)+red(stepDesc)+f.line(feature.Uri, astStep.Location)) fmt.Fprintln(f.out, s(f.indent*3)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n") } } @@ -227,7 +231,9 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in printExampleHeader := exampleTable.TableBody[0].Id == exampleRow.Id firstExamplesTable := astScenario.Examples[0].Location.Line == exampleTable.Location.Line - firstExecutedScenarioStep := len(f.lastFeature().lastPickleResult().stepResults) == backgroundSteps+1 + pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + + firstExecutedScenarioStep := len(pickleStepResults) == backgroundSteps+1 if firstExamplesTable && printExampleHeader && firstExecutedScenarioStep { f.printScenarioHeader(astScenario, maxLength-scenarioHeaderLength) } @@ -237,32 +243,31 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in return } - lastStep := len(f.lastFeature().lastPickleResult().stepResults) == len(pickle.Steps) + lastStep := len(pickleStepResults) == len(pickle.Steps) if !lastStep { // do not print examples unless all steps has finished return } - lastPickleResult := f.lastFeature().lastPickleResult() - for _, result := range lastPickleResult.stepResults { + for _, result := range pickleStepResults { // determine example row status switch { - case result.status == failed: + case result.Status == failed: errorMsg = result.err.Error() - clr = result.status.clr() - case result.status == undefined || result.status == pending: - clr = result.status.clr() - case result.status == skipped && clr == nil: + clr = result.Status.clr() + case result.Status == undefined || result.Status == pending: + clr = result.Status.clr() + case result.Status == skipped && clr == nil: clr = cyan } if firstExamplesTable && printExampleHeader { // in first example, we need to print steps - var text string - pickleStep := f.storage.mustGetPickleStep(result.pickleStepID) + pickleStep := f.storage.mustGetPickleStep(result.PickleStepID) astStep := f.findStep(pickleStep.AstNodeIds[0]) + var text = "" if result.def != nil { if m := outlinePlaceholderRegexp.FindAllStringIndex(astStep.Text, -1); len(m) > 0 { var pos int @@ -277,16 +282,13 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in text = cyan(astStep.Text) } - pickle := f.storage.mustGetPickle(lastPickleResult.pickleID) - _, maxLength := f.scenarioLengths(pickle.AstNodeIds[0]) stepLength := f.lengthPickleStep(astStep.Keyword, astStep.Text) text += s(maxLength - stepLength) text += " " + blackb("# "+result.def.definitionID()) - } else { - text = cyan(astStep.Text) } + // print the step outline fmt.Fprintln(f.out, s(f.indent*2)+cyan(strings.TrimSpace(astStep.Keyword))+" "+text) @@ -333,9 +335,7 @@ func (f *pretty) printTableHeader(row *messages.GherkinDocument_Feature_TableRow f.printTableRow(row, max, cyan) } -func (f *pretty) printStep(pickle *messages.Pickle, result *stepResult) { - pickleStep := f.storage.mustGetPickleStep(result.pickleStepID) - +func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep) { astBackground := f.findBackground(pickle.AstNodeIds[0]) astScenario := f.findScenario(pickle.AstNodeIds[0]) astStep := f.findStep(pickleStep.AstNodeIds[0]) @@ -345,14 +345,25 @@ func (f *pretty) printStep(pickle *messages.Pickle, result *stepResult) { backgroundSteps = len(astBackground.Steps) } - astBackgroundStep := backgroundSteps > 0 && backgroundSteps >= len(f.lastFeature().lastPickleResult().stepResults) + pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pickle.Id) + astBackgroundStep := backgroundSteps > 0 && backgroundSteps >= len(pickleStepResults) if astBackgroundStep { - if len(f.lastFeature().pickleResults) > 1 { + pickles := f.storage.mustGetPickles(pickle.Uri) + + var pickleResults []pickleResult + for _, pickle := range pickles { + pr, err := f.storage.getPickleResult(pickle.Id) + if err == nil { + pickleResults = append(pickleResults, pr) + } + } + + if len(pickleResults) > 1 { return } - firstExecutedBackgroundStep := astBackground != nil && len(f.lastFeature().lastPickleResult().stepResults) == 1 + firstExecutedBackgroundStep := astBackground != nil && len(pickleStepResults) == 1 if firstExecutedBackgroundStep { fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astBackground.Keyword, astBackground.Name)) } @@ -366,15 +377,16 @@ func (f *pretty) printStep(pickle *messages.Pickle, result *stepResult) { scenarioHeaderLength, maxLength := f.scenarioLengths(pickle.AstNodeIds[0]) stepLength := f.lengthPickleStep(astStep.Keyword, pickleStep.Text) - firstExecutedScenarioStep := len(f.lastFeature().lastPickleResult().stepResults) == backgroundSteps+1 + firstExecutedScenarioStep := len(pickleStepResults) == backgroundSteps+1 if !astBackgroundStep && firstExecutedScenarioStep { f.printScenarioHeader(astScenario, maxLength-scenarioHeaderLength) } - text := s(f.indent*2) + result.status.clr()(strings.TrimSpace(astStep.Keyword)) + " " + result.status.clr()(pickleStep.Text) - if result.def != nil { + 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 { text += s(maxLength - stepLength + 1) - text += blackb("# " + result.def.definitionID()) + text += blackb("# " + pickleStepResult.def.definitionID()) } fmt.Fprintln(f.out, text) @@ -386,11 +398,11 @@ func (f *pretty) printStep(pickle *messages.Pickle, result *stepResult) { f.printDocString(docString) } - if result.err != nil { - fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", result.err))) + if pickleStepResult.err != nil { + fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", pickleStepResult.err))) } - if result.status == pending { + if pickleStepResult.Status == pending { fmt.Fprintln(f.out, s(f.indent*3)+yellow("TODO: write pending definition")) } } diff --git a/fmt_progress.go b/fmt_progress.go index 98d9e95..2bfa531 100644 --- a/fmt_progress.go +++ b/fmt_progress.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "math" + "sort" "strings" "github.com/cucumber/messages-go/v10" @@ -39,10 +40,14 @@ func (f *progress) Summary() { } var failedStepsOutput []string - for _, sr := range f.findStepResults(failed) { - if sr.status == failed { - pickle := f.storage.mustGetPickle(sr.pickleID) - pickleStep := f.storage.mustGetPickleStep(sr.pickleStepID) + + 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) sc := f.findScenario(pickle.AstNodeIds[0]) scenarioDesc := fmt.Sprintf("%s: %s", sc.Keyword, pickle.Name) @@ -71,8 +76,10 @@ func (f *progress) Summary() { f.basefmt.Summary() } -func (f *progress) step(res *stepResult) { - switch res.status { +func (f *progress) step(pickleStepID string) { + pickleStepResult := f.storage.mustGetPickleStepResult(pickleStepID) + + switch pickleStepResult.Status { case passed: fmt.Fprint(f.out, green(".")) case skipped: @@ -98,7 +105,7 @@ func (f *progress) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleS f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(step.Id) } func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -107,7 +114,7 @@ func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_Pickle f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(step.Id) } func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -116,7 +123,7 @@ func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pick f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(step.Id) } func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { @@ -125,7 +132,7 @@ func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleS f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(step.Id) } func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -134,7 +141,7 @@ func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_Pickle f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(step.Id) } func (f *progress) Sync(cf ConcurrentFormatter) { diff --git a/run.go b/run.go index 5bf49ca..572c0ae 100644 --- a/run.go +++ b/run.go @@ -26,13 +26,15 @@ type scenarioInitializer func(*ScenarioContext) type runner struct { randomSeed int64 stopOnFailure, strict bool - features []*feature - fmt Formatter - initializer initializer - testSuiteInitializer testSuiteInitializer - scenarioInitializer scenarioInitializer + + features []*feature + + initializer initializer + testSuiteInitializer testSuiteInitializer + scenarioInitializer scenarioInitializer storage *storage + fmt Formatter } func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool) { @@ -67,21 +69,24 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool go func(fail *bool, feat *feature) { var fmtCopy Formatter + defer func() { <-queue // free a space in queue }() + if r.stopOnFailure && *fail { return } + suite := &Suite{ fmt: r.fmt, randomSeed: r.randomSeed, stopOnFailure: r.stopOnFailure, strict: r.strict, features: []*feature{feat}, + storage: r.storage, } - suite.fmt = r.fmt if useFmtCopy { fmtCopy = formatterFn() suite.fmt = fmtCopy @@ -107,11 +112,13 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool } suite.run() + if suite.failed { copyLock.Lock() *fail = true copyLock.Unlock() } + if useFmtCopy { copyLock.Lock() diff --git a/storage.go b/storage.go index e24039a..5e3ecdc 100644 --- a/storage.go +++ b/storage.go @@ -1,6 +1,8 @@ package godog import ( + "fmt" + "github.com/cucumber/messages-go/v10" "github.com/hashicorp/go-memdb" ) @@ -9,11 +11,20 @@ const ( writeMode bool = true readMode bool = false - tablePickle string = "pickle" - tablePickleIndexID string = "id" + tablePickle string = "pickle" + tablePickleIndexID string = "id" + tablePickleIndexURI string = "uri" tablePickleStep string = "pickle_step" tablePickleStepIndexID string = "id" + + tablePickleResult string = "pickle_result" + tablePickleResultIndexPickleID string = "id" + + tablePickleStepResult string = "pickle_step_result" + tablePickleStepResultIndexPickleStepID string = "id" + tablePickleStepResultIndexPickleID string = "pickle_id" + tablePickleStepResultIndexStatus string = "status" ) type storage struct { @@ -32,6 +43,11 @@ func newStorage() *storage { Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Id"}, }, + tablePickleIndexURI: { + Name: tablePickleIndexURI, + Unique: false, + Indexer: &memdb.StringFieldIndex{Field: "Uri"}, + }, }, }, tablePickleStep: { @@ -44,6 +60,36 @@ func newStorage() *storage { }, }, }, + tablePickleResult: { + Name: tablePickleResult, + Indexes: map[string]*memdb.IndexSchema{ + tablePickleResultIndexPickleID: { + Name: tablePickleResultIndexPickleID, + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "PickleID"}, + }, + }, + }, + tablePickleStepResult: { + Name: tablePickleStepResult, + Indexes: map[string]*memdb.IndexSchema{ + tablePickleStepResultIndexPickleStepID: { + Name: tablePickleStepResultIndexPickleStepID, + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "PickleStepID"}, + }, + tablePickleStepResultIndexPickleID: { + Name: tablePickleStepResultIndexPickleID, + Unique: false, + Indexer: &memdb.StringFieldIndex{Field: "PickleID"}, + }, + tablePickleStepResultIndexStatus: { + Name: tablePickleStepResultIndexStatus, + Unique: false, + Indexer: &memdb.IntFieldIndex{Field: "Status"}, + }, + }, + }, }, } @@ -56,21 +102,20 @@ func newStorage() *storage { return &storage{db} } -func (s *storage) mustInsertPickle(p *messages.Pickle) (err error) { +func (s *storage) mustInsertPickle(p *messages.Pickle) { txn := s.db.Txn(writeMode) - if err = txn.Insert(tablePickle, p); err != nil { + if err := txn.Insert(tablePickle, p); err != nil { panic(err) } for _, step := range p.Steps { - if err = txn.Insert(tablePickleStep, step); err != nil { + if err := txn.Insert(tablePickleStep, step); err != nil { panic(err) } } txn.Commit() - return } func (s *storage) mustGetPickle(id string) *messages.Pickle { @@ -88,6 +133,22 @@ func (s *storage) mustGetPickle(id string) *messages.Pickle { return v.(*messages.Pickle) } +func (s *storage) mustGetPickles(uri string) (ps []*messages.Pickle) { + txn := s.db.Txn(readMode) + defer txn.Abort() + + it, err := txn.Get(tablePickle, tablePickleIndexURI, uri) + if err != nil { + panic(err) + } + + for v := it.Next(); v != nil; v = it.Next() { + ps = append(ps, v.(*messages.Pickle)) + } + + return +} + func (s *storage) mustGetPickleStep(id string) *messages.Pickle_PickleStep { txn := s.db.Txn(readMode) defer txn.Abort() @@ -102,3 +163,109 @@ func (s *storage) mustGetPickleStep(id string) *messages.Pickle_PickleStep { return v.(*messages.Pickle_PickleStep) } + +func (s *storage) mustInsertPickleResult(pr pickleResult) { + txn := s.db.Txn(writeMode) + + if err := txn.Insert(tablePickleResult, pr); err != nil { + panic(err) + } + + txn.Commit() +} + +func (s *storage) mustInsertPickleStepResult(psr pickleStepResult) { + txn := s.db.Txn(writeMode) + + if err := txn.Insert(tablePickleStepResult, psr); err != nil { + panic(err) + } + + txn.Commit() +} + +func (s *storage) mustGetPickleResult(id string) pickleResult { + pr, err := s.getPickleResult(id) + if err != nil { + panic(err) + } + + return pr +} + +func (s *storage) getPickleResult(id string) (_ pickleResult, err error) { + txn := s.db.Txn(readMode) + defer txn.Abort() + + v, err := txn.First(tablePickleResult, tablePickleResultIndexPickleID, id) + if err != nil { + return + } else if v == nil { + err = fmt.Errorf("Couldn't find pickle result with ID: %s", id) + return + } + + return v.(pickleResult), nil +} + +func (s *storage) mustGetPickleResults() (prs []pickleResult) { + txn := s.db.Txn(readMode) + defer txn.Abort() + + it, err := txn.Get(tablePickleResult, tablePickleResultIndexPickleID) + if err != nil { + panic(err) + } + + for v := it.Next(); v != nil; v = it.Next() { + prs = append(prs, v.(pickleResult)) + } + + return prs +} + +func (s *storage) mustGetPickleStepResult(id string) pickleStepResult { + txn := s.db.Txn(readMode) + defer txn.Abort() + + v, err := txn.First(tablePickleStepResult, tablePickleStepResultIndexPickleStepID, id) + if err != nil { + panic(err) + } else if v == nil { + panic("Couldn't find pickle step result with ID: " + id) + } + + return v.(pickleStepResult) +} + +func (s *storage) mustGetPickleStepResultsByPickleID(pickleID string) (psrs []pickleStepResult) { + txn := s.db.Txn(readMode) + defer txn.Abort() + + it, err := txn.Get(tablePickleStepResult, tablePickleStepResultIndexPickleID, pickleID) + if err != nil { + panic(err) + } + + for v := it.Next(); v != nil; v = it.Next() { + psrs = append(psrs, v.(pickleStepResult)) + } + + return psrs +} + +func (s *storage) mustGetPickleStepResultsByStatus(status stepResultStatus) (psrs []pickleStepResult) { + txn := s.db.Txn(readMode) + defer txn.Abort() + + it, err := txn.Get(tablePickleStepResult, tablePickleStepResultIndexStatus, status) + if err != nil { + panic(err) + } + + for v := it.Next(); v != nil; v = it.Next() { + psrs = append(psrs, v.(pickleStepResult)) + } + + return psrs +} diff --git a/suite.go b/suite.go index 4462a32..89e662b 100644 --- a/suite.go +++ b/suite.go @@ -23,12 +23,10 @@ var typeOfBytes = reflect.TypeOf([]byte(nil)) type feature struct { *messages.GherkinDocument - pickles []*messages.Pickle - pickleResults []*pickleResult + pickles []*messages.Pickle time time.Time content []byte - path string order int } @@ -100,51 +98,44 @@ func (f feature) startedAt() time.Time { return f.time } -func (f feature) finishedAt() time.Time { - if len(f.pickleResults) == 0 { - return f.startedAt() - } - - return f.pickleResults[len(f.pickleResults)-1].finishedAt() -} - -func (f feature) appendStepResult(s *stepResult) { - pickles := f.pickleResults[len(f.pickleResults)-1] - pickles.stepResults = append(pickles.stepResults, s) -} - -func (f feature) lastPickleResult() *pickleResult { - return f.pickleResults[len(f.pickleResults)-1] -} - -func (f feature) lastStepResult() *stepResult { - last := f.lastPickleResult() - return last.stepResults[len(last.stepResults)-1] -} - type sortByName []*feature func (s sortByName) Len() int { return len(s) } func (s sortByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name } func (s sortByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -type pickleResult struct { - pickleID string +type sortPicklesByID []*messages.Pickle - time time.Time - stepResults []*stepResult +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 (s pickleResult) startedAt() time.Time { - return s.time +type sortPickleStepResultsByPickleStepID []pickleStepResult + +func (s sortPickleStepResultsByPickleStepID) Len() int { return len(s) } +func (s sortPickleStepResultsByPickleStepID) Less(i, j int) bool { + iID := mustConvertStringToInt(s[i].PickleStepID) + jID := mustConvertStringToInt(s[j].PickleStepID) + return iID < jID } +func (s sortPickleStepResultsByPickleStepID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s pickleResult) finishedAt() time.Time { - if len(s.stepResults) == 0 { - return s.startedAt() +func mustConvertStringToInt(s string) int { + i, err := strconv.Atoi(s) + if err != nil { + panic(err) } - return s.stepResults[len(s.stepResults)-1].time + return i +} + +type pickleResult struct { + PickleID string + StartedAt time.Time } // ErrUndefined is returned in case if step definition was not found @@ -172,7 +163,9 @@ var ErrPending = fmt.Errorf("step implementation is pending") type Suite struct { steps []*StepDefinition features []*feature - fmt Formatter + + fmt Formatter + storage *storage failed bool randomSeed int64 @@ -417,12 +410,24 @@ func (s *Suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleSte return } + sr := newStepResult(pickle.Id, step.Id, match) + switch err { case nil: + sr.Status = passed + s.storage.mustInsertPickleStepResult(sr) + s.fmt.Passed(pickle, step, match) case ErrPending: + sr.Status = pending + s.storage.mustInsertPickleStepResult(sr) + s.fmt.Pending(pickle, step, match) default: + sr.Status = failed + sr.err = err + s.storage.mustInsertPickleStepResult(sr) + s.fmt.Failed(pickle, step, match, err) } @@ -445,11 +450,20 @@ func (s *Suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleSte undefined: undef, } } + + sr := newStepResult(pickle.Id, step.Id, match) + sr.Status = 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) + s.fmt.Skipped(pickle, step, match) return nil } @@ -575,7 +589,7 @@ func (s *Suite) runFeature(f *feature) { } } - s.fmt.Feature(f.GherkinDocument, f.path, f.content) + s.fmt.Feature(f.GherkinDocument, f.Uri, f.content) defer func() { if !isEmptyFeature(f.pickles) { @@ -613,6 +627,9 @@ 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) + s.fmt.Pickle(pickle) return ErrUndefined } @@ -622,6 +639,9 @@ func (s *Suite) runPickle(pickle *messages.Pickle) (err error) { f(pickle) } + pr := pickleResult{PickleID: pickle.Id, StartedAt: timeNowFunc()} + s.storage.mustInsertPickleResult(pr) + s.fmt.Pickle(pickle) // scenario @@ -673,6 +693,7 @@ func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) { if err != nil { return nil, err } + defer reader.Close() var buf bytes.Buffer @@ -681,14 +702,11 @@ func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) { return nil, fmt.Errorf("%s - %v", path, err) } + gherkinDocument.Uri = path pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc) - return &feature{ - GherkinDocument: gherkinDocument, - pickles: pickles, - content: buf.Bytes(), - path: path, - }, nil + f := feature{GherkinDocument: gherkinDocument, pickles: pickles, content: buf.Bytes()} + return &f, nil } func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) { @@ -710,6 +728,7 @@ func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) { if err != nil { return err } + features = append(features, feat) return nil }) @@ -751,10 +770,12 @@ func parsePath(path string) ([]*feature, error) { } func parseFeatures(filter string, paths []string) ([]*feature, error) { - byPath := make(map[string]*feature) var order int + + uniqueFeatureURI := make(map[string]*feature) for _, path := range paths { feats, err := parsePath(path) + switch { case os.IsNotExist(err): return nil, fmt.Errorf(`feature path "%s" is not available`, path) @@ -765,50 +786,49 @@ func parseFeatures(filter string, paths []string) ([]*feature, error) { } for _, ft := range feats { - if _, duplicate := byPath[ft.path]; duplicate { + if _, duplicate := uniqueFeatureURI[ft.Uri]; duplicate { continue } ft.order = order order++ - byPath[ft.path] = ft + uniqueFeatureURI[ft.Uri] = ft } } - return filterFeatures(filter, byPath), nil + return filterFeatures(filter, uniqueFeatureURI), nil } -type sortByOrderGiven []*feature +type sortFeaturesByOrder []*feature -func (s sortByOrderGiven) Len() int { return len(s) } -func (s sortByOrderGiven) Less(i, j int) bool { return s[i].order < s[j].order } -func (s sortByOrderGiven) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s sortFeaturesByOrder) Len() int { return len(s) } +func (s sortFeaturesByOrder) Less(i, j int) bool { return s[i].order < s[j].order } +func (s sortFeaturesByOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func filterFeatures(tags string, collected map[string]*feature) (features []*feature) { for _, ft := range collected { - applyTagFilter(tags, ft) + ft.pickles = applyTagFilter(tags, ft.pickles) if ft.Feature != nil { features = append(features, ft) } } - sort.Sort(sortByOrderGiven(features)) + sort.Sort(sortFeaturesByOrder(features)) return features } -func applyTagFilter(tags string, ft *feature) { +func applyTagFilter(tags string, pickles []*messages.Pickle) (result []*messages.Pickle) { if len(tags) == 0 { - return + return pickles } - var pickles []*messages.Pickle - for _, pickle := range ft.pickles { + for _, pickle := range pickles { if matchesTags(tags, pickle.Tags) { - pickles = append(pickles, pickle) + result = append(result, pickle) } } - ft.pickles = pickles + return } diff --git a/suite_context.go b/suite_context.go index fffd3c6..2daf8e2 100644 --- a/suite_context.go +++ b/suite_context.go @@ -57,7 +57,6 @@ func SuiteContext(s *Suite, additionalContextInitializers ...func(suite *Suite)) s.Step(`^(?:a )?failing step`, c.aFailingStep) s.Step(`^this step should fail`, c.aFailingStep) s.Step(`^the following steps? should be (passed|failed|skipped|undefined|pending):`, c.followingStepsShouldHave) - s.Step(`^all steps should (?:be|have|have been) (passed|failed|skipped|undefined|pending)$`, c.allStepsShouldHave) s.Step(`^the undefined step snippets should be:$`, c.theUndefinedStepSnippetsShouldBe) // event stream @@ -180,18 +179,18 @@ func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error { } for _, feat := range s.testedSuite.features { - applyTagFilter(tags, feat) + feat.pickles = applyTagFilter(tags, feat.pickles) } - st := newStorage() + s.testedSuite.storage = newStorage() for _, feat := range s.testedSuite.features { for _, pickle := range feat.pickles { - st.mustInsertPickle(pickle) + s.testedSuite.storage.mustInsertPickle(pickle) } } fmt := newBaseFmt("godog", &s.out) - fmt.setStorage(st) + fmt.setStorage(s.testedSuite.storage) s.testedSuite.fmt = fmt s.testedSuite.fmt.TestRunStarted() @@ -211,16 +210,16 @@ func (s *suiteContext) iRunFeatureSuiteWithFormatter(name string) error { return fmt.Errorf(`formatter "%s" is not available`, name) } - st := newStorage() + s.testedSuite.storage = newStorage() for _, feat := range s.testedSuite.features { for _, pickle := range feat.pickles { - st.mustInsertPickle(pickle) + s.testedSuite.storage.mustInsertPickle(pickle) } } s.testedSuite.fmt = f("godog", colors.Uncolored(&s.out)) if fmt, ok := s.testedSuite.fmt.(storageFormatter); ok { - fmt.setStorage(st) + fmt.setStorage(s.testedSuite.storage) } s.testedSuite.fmt.TestRunStarted() @@ -294,28 +293,28 @@ func (s *suiteContext) followingStepsShouldHave(status string, steps *DocString) switch status { case "passed": - for _, st := range f.findStepResults(passed) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(passed) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "failed": - for _, st := range f.findStepResults(failed) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(failed) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "skipped": - for _, st := range f.findStepResults(skipped) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(skipped) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "undefined": - for _, st := range f.findStepResults(undefined) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(undefined) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "pending": - for _, st := range f.findStepResults(pending) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(pending) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } default: @@ -356,42 +355,6 @@ func (s *suiteContext) followingStepsShouldHave(status string, steps *DocString) return fmt.Errorf("the steps: %s - are not %s", strings.Join(unmatched, ", "), status) } -func (s *suiteContext) allStepsShouldHave(status string) error { - f, ok := s.testedSuite.fmt.(*basefmt) - if !ok { - return fmt.Errorf("this step requires *basefmt, but there is: %T", s.testedSuite.fmt) - } - - total := len(f.findStepResults(passed)) + - len(f.findStepResults(failed)) + - len(f.findStepResults(skipped)) + - len(f.findStepResults(undefined)) + - len(f.findStepResults(pending)) - - var actual int - - switch status { - case "passed": - actual = len(f.findStepResults(passed)) - case "failed": - actual = len(f.findStepResults(failed)) - case "skipped": - actual = len(f.findStepResults(skipped)) - case "undefined": - actual = len(f.findStepResults(undefined)) - case "pending": - actual = len(f.findStepResults(pending)) - default: - return fmt.Errorf("unexpected step status wanted: %s", status) - } - - if total > actual { - return fmt.Errorf("number of expected %s steps: %d is less than actual %s steps: %d", status, total, status, actual) - } - - return nil -} - func (s *suiteContext) iAmListeningToSuiteEvents() error { s.testedSuite.BeforeSuite(func() { s.events = append(s.events, &firedEvent{"BeforeSuite", []interface{}{}}) @@ -435,8 +398,10 @@ func (s *suiteContext) aFailingStep() error { // parse a given feature file body as a feature func (s *suiteContext) aFeatureFile(path string, body *DocString) error { gd, err := gherkin.ParseGherkinDocument(strings.NewReader(body.Content), (&messages.Incrementing{}).NewId) + gd.Uri = path + pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) - s.testedSuite.features = append(s.testedSuite.features, &feature{GherkinDocument: gd, pickles: pickles, path: path}) + s.testedSuite.features = append(s.testedSuite.features, &feature{GherkinDocument: gd, pickles: pickles}) return err } @@ -479,7 +444,7 @@ func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *DocString) err var actual []string for _, ft := range s.testedSuite.features { - actual = append(actual, ft.path) + actual = append(actual, ft.Uri) } if len(expected) != len(actual) { diff --git a/suite_context_test.go b/suite_context_test.go index db4a8ab..b4adcb5 100644 --- a/suite_context_test.go +++ b/suite_context_test.go @@ -41,7 +41,6 @@ func InitializeScenario(ctx *ScenarioContext) { ctx.Step(`^(?:a )?failing step`, tc.aFailingStep) ctx.Step(`^this step should fail`, tc.aFailingStep) ctx.Step(`^the following steps? should be (passed|failed|skipped|undefined|pending):`, tc.followingStepsShouldHave) - ctx.Step(`^all steps should (?:be|have|have been) (passed|failed|skipped|undefined|pending)$`, tc.allStepsShouldHave) ctx.Step(`^the undefined step snippets should be:$`, tc.theUndefinedStepSnippetsShouldBe) // event stream @@ -142,18 +141,18 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithTags(tags string) error { } for _, feat := range tc.testedSuite.features { - applyTagFilter(tags, feat) + feat.pickles = applyTagFilter(tags, feat.pickles) } - st := newStorage() + tc.testedSuite.storage = newStorage() for _, feat := range tc.testedSuite.features { for _, pickle := range feat.pickles { - st.mustInsertPickle(pickle) + tc.testedSuite.storage.mustInsertPickle(pickle) } } fmt := newBaseFmt("godog", &tc.out) - fmt.setStorage(st) + fmt.setStorage(tc.testedSuite.storage) tc.testedSuite.fmt = fmt tc.testedSuite.fmt.TestRunStarted() @@ -173,16 +172,16 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithFormatter(name string) erro return fmt.Errorf(`formatter "%s" is not available`, name) } - st := newStorage() + tc.testedSuite.storage = newStorage() for _, feat := range tc.testedSuite.features { for _, pickle := range feat.pickles { - st.mustInsertPickle(pickle) + tc.testedSuite.storage.mustInsertPickle(pickle) } } tc.testedSuite.fmt = f("godog", colors.Uncolored(&tc.out)) if fmt, ok := tc.testedSuite.fmt.(storageFormatter); ok { - fmt.setStorage(st) + fmt.setStorage(tc.testedSuite.storage) } tc.testedSuite.fmt.TestRunStarted() @@ -256,28 +255,28 @@ func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps * switch status { case "passed": - for _, st := range f.findStepResults(passed) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(passed) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "failed": - for _, st := range f.findStepResults(failed) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(failed) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "skipped": - for _, st := range f.findStepResults(skipped) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(skipped) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "undefined": - for _, st := range f.findStepResults(undefined) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(undefined) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } case "pending": - for _, st := range f.findStepResults(pending) { - pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + for _, st := range f.storage.mustGetPickleStepResultsByStatus(pending) { + pickleStep := f.storage.mustGetPickleStep(st.PickleStepID) actual = append(actual, pickleStep.Text) } default: @@ -318,42 +317,6 @@ func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps * return fmt.Errorf("the steps: %s - are not %s", strings.Join(unmatched, ", "), status) } -func (tc *godogFeaturesScenario) allStepsShouldHave(status string) error { - f, ok := tc.testedSuite.fmt.(*basefmt) - if !ok { - return fmt.Errorf("this step requires *basefmt, but there is: %T", tc.testedSuite.fmt) - } - - total := len(f.findStepResults(passed)) + - len(f.findStepResults(failed)) + - len(f.findStepResults(skipped)) + - len(f.findStepResults(undefined)) + - len(f.findStepResults(pending)) - - var actual int - - switch status { - case "passed": - actual = len(f.findStepResults(passed)) - case "failed": - actual = len(f.findStepResults(failed)) - case "skipped": - actual = len(f.findStepResults(skipped)) - case "undefined": - actual = len(f.findStepResults(undefined)) - case "pending": - actual = len(f.findStepResults(pending)) - default: - return fmt.Errorf("unexpected step status wanted: %s", status) - } - - if total > actual { - return fmt.Errorf("number of expected %s steps: %d is less than actual %s steps: %d", status, total, status, actual) - } - - return nil -} - func (tc *godogFeaturesScenario) iAmListeningToSuiteEvents() error { tc.testedSuite.BeforeSuite(func() { tc.events = append(tc.events, &firedEvent{"BeforeSuite", []interface{}{}}) @@ -397,8 +360,10 @@ func (tc *godogFeaturesScenario) aFailingStep() error { // parse a given feature file body as a feature func (tc *godogFeaturesScenario) aFeatureFile(path string, body *DocString) error { gd, err := gherkin.ParseGherkinDocument(strings.NewReader(body.Content), (&messages.Incrementing{}).NewId) + gd.Uri = path + pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) - tc.testedSuite.features = append(tc.testedSuite.features, &feature{GherkinDocument: gd, pickles: pickles, path: path}) + tc.testedSuite.features = append(tc.testedSuite.features, &feature{GherkinDocument: gd, pickles: pickles}) return err } @@ -441,7 +406,7 @@ func (tc *godogFeaturesScenario) iShouldHaveNumFeatureFiles(num int, files *DocS var actual []string for _, ft := range tc.testedSuite.features { - actual = append(actual, ft.path) + actual = append(actual, ft.Uri) } if len(expected) != len(actual) { diff --git a/test_context_test.go b/test_context_test.go index 0d22af3..76d4c0b 100644 --- a/test_context_test.go +++ b/test_context_test.go @@ -33,6 +33,11 @@ func Test_TestContext(t *testing.T) { }, } + r.storage = newStorage() + for _, pickle := range pickles { + r.storage.mustInsertPickle(pickle) + } + failed := r.concurrent(1, func() Formatter { return progressFunc("progress", w) }) require.False(t, failed)