diff --git a/fmt.go b/fmt.go index 610047f..ae02fdd 100644 --- a/fmt.go +++ b/fmt.go @@ -91,6 +91,10 @@ type ConcurrentFormatter interface { Sync(ConcurrentFormatter) } +type storageFormatter interface { + setStorage(*storage) +} + // FormatterFunc builds a formatter with given // suite name and io.Writer to record output type FormatterFunc func(string, io.Writer) Formatter @@ -140,13 +144,14 @@ type stepResult struct { time time.Time err error - owner *messages.Pickle - step *messages.Pickle_PickleStep - def *StepDefinition + pickleID string + pickleStepID string + + def *StepDefinition } -func newStepResult(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) *stepResult { - return &stepResult{time: timeNowFunc(), owner: pickle, step: step, def: match} +func newStepResult(pickleID, pickleStepID string, match *StepDefinition) *stepResult { + return &stepResult{time: timeNowFunc(), pickleID: pickleID, pickleStepID: pickleStepID, def: match} } func newBaseFmt(suite string, out io.Writer) *basefmt { @@ -166,6 +171,8 @@ type basefmt struct { owner interface{} indent int + storage *storage + started time.Time features []*feature @@ -173,6 +180,10 @@ type basefmt struct { lock *sync.Mutex } +func (f *basefmt) setStorage(st *storage) { + f.storage = st +} + func (f *basefmt) lastFeature() *feature { return f.features[len(f.features)-1] } @@ -245,7 +256,7 @@ func (f *basefmt) Pickle(p *messages.Pickle) { feature := f.features[len(f.features)-1] - pr := pickleResult{name: p.Name, astNodeIDs: p.AstNodeIds, time: timeNowFunc()} + pr := pickleResult{pickleID: p.Id, time: timeNowFunc()} feature.pickleResults = append(feature.pickleResults, &pr) } @@ -264,7 +275,7 @@ func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - s := newStepResult(pickle, step, match) + s := newStepResult(pickle.Id, step.Id, match) s.status = passed f.lastFeature().appendStepResult(s) } @@ -273,7 +284,7 @@ func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleS f.lock.Lock() defer f.lock.Unlock() - s := newStepResult(pickle, step, match) + s := newStepResult(pickle.Id, step.Id, match) s.status = skipped f.lastFeature().appendStepResult(s) } @@ -282,7 +293,7 @@ func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pickl f.lock.Lock() defer f.lock.Unlock() - s := newStepResult(pickle, step, match) + s := newStepResult(pickle.Id, step.Id, match) s.status = undefined f.lastFeature().appendStepResult(s) } @@ -291,7 +302,7 @@ func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - s := newStepResult(pickle, step, match) + s := newStepResult(pickle.Id, step.Id, match) s.status = failed s.err = err f.lastFeature().appendStepResult(s) @@ -301,7 +312,7 @@ func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleS f.lock.Lock() defer f.lock.Unlock() - s := newStepResult(pickle, step, match) + s := newStepResult(pickle.Id, step.Id, match) s.status = pending f.lastFeature().appendStepResult(s) } @@ -450,8 +461,10 @@ func (f *basefmt) snippets() string { var snips []undefinedSnippet // build snippets for _, u := range undefinedStepResults { - steps := []string{u.step.Text} - arg := u.step.Argument + pickleStep := f.storage.mustGetPickleStep(u.pickleStepID) + + steps := []string{pickleStep.Text} + arg := pickleStep.Argument if u.def != nil { steps = u.def.undefined arg = nil diff --git a/fmt_cucumber.go b/fmt_cucumber.go index 0179881..e370a4c 100644 --- a/fmt_cucumber.go +++ b/fmt_cucumber.go @@ -79,7 +79,9 @@ func (f *cukefmt) buildCukeElements(pickleResults []*pickleResult) (res []cukeEl res = make([]cukeElement, len(pickleResults)) for idx, pickleResult := range pickleResults { - cukeElement := f.buildCukeElement(pickleResult.name, pickleResult.astNodeIDs) + pickle := f.storage.mustGetPickle(pickleResult.pickleID) + + cukeElement := f.buildCukeElement(pickle.Name, pickle.AstNodeIds) stepStartedAt := pickleResult.startedAt() @@ -237,19 +239,21 @@ func (f *cukefmt) buildCukeElement(pickleName string, pickleAstNodeIDs []string) } func (f *cukefmt) buildCukeStep(stepResult *stepResult) (cukeStep cukeStep) { - step := f.findStep(stepResult.step.AstNodeIds[0]) + pickle := f.storage.mustGetPickle(stepResult.pickleID) + pickleStep := f.storage.mustGetPickleStep(stepResult.pickleStepID) + step := f.findStep(pickleStep.AstNodeIds[0]) line := step.Location.Line - if len(stepResult.owner.AstNodeIds) == 2 { - _, row := f.findExample(stepResult.owner.AstNodeIds[1]) + if len(pickle.AstNodeIds) == 2 { + _, row := f.findExample(pickle.AstNodeIds[1]) line = row.Location.Line } - cukeStep.Name = stepResult.step.Text + cukeStep.Name = pickleStep.Text cukeStep.Line = int(line) cukeStep.Keyword = step.Keyword - arg := stepResult.step.Argument + arg := pickleStep.Argument if arg.GetDocString() != nil && step.GetDocString() != nil { cukeStep.Docstring = &cukeDocstring{} @@ -279,7 +283,7 @@ func (f *cukefmt) buildCukeStep(stepResult *stepResult) (cukeStep cukeStep) { } if stepResult.status == undefined || stepResult.status == pending { - cukeStep.Match.Location = fmt.Sprintf("%s:%d", stepResult.owner.Uri, step.Location.Line) + cukeStep.Match.Location = fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line) } return cukeStep diff --git a/fmt_events.go b/fmt_events.go index 96eda5c..ad104c3 100644 --- a/fmt_events.go +++ b/fmt_events.go @@ -145,8 +145,9 @@ func (f *events) Copy(cf ConcurrentFormatter) { } } -func (f *events) step(res *stepResult) { - step := f.findStep(res.step.AstNodeIds[0]) +func (f *events) step(pickle *messages.Pickle, res *stepResult) { + pickleStep := f.storage.mustGetPickleStep(res.pickleStepID) + step := f.findStep(pickleStep.AstNodeIds[0]) var errMsg string if res.err != nil { @@ -160,13 +161,13 @@ func (f *events) step(res *stepResult) { Summary string `json:"summary,omitempty"` }{ "TestStepFinished", - fmt.Sprintf("%s:%d", res.owner.Uri, step.Location.Line), + fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line), timeNowFunc().UnixNano() / nanoSec, res.status.String(), errMsg, }) - if isLastStep(res.owner, res.step) { + if isLastStep(pickle, pickleStep) { var status string for _, stepResult := range f.lastFeature().lastPickleResult().stepResults { @@ -189,7 +190,7 @@ func (f *events) step(res *stepResult) { Status string `json:"status"` }{ "TestCaseFinished", - f.scenarioLocation(res.owner), + f.scenarioLocation(pickle), timeNowFunc().UnixNano() / nanoSec, status, }) @@ -249,7 +250,7 @@ func (f *events) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(pickle, f.lastStepResult()) } func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -258,7 +259,7 @@ func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(pickle, f.lastStepResult()) } func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -267,7 +268,7 @@ func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pickle f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(pickle, f.lastStepResult()) } func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { @@ -276,7 +277,7 @@ func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(pickle, f.lastStepResult()) } func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -285,7 +286,7 @@ func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.step(f.lastStepResult()) + f.step(pickle, f.lastStepResult()) } func (f *events) scenarioLocation(pickle *messages.Pickle) string { diff --git a/fmt_junit.go b/fmt_junit.go index bfd7ea9..513b7c1 100644 --- a/fmt_junit.go +++ b/fmt_junit.go @@ -23,7 +23,7 @@ type junitFormatter struct { } func (f *junitFormatter) Summary() { - suite := buildJUNITPackageSuite(f.suiteName, f.started, f.features) + suite := f.buildJUNITPackageSuite() _, err := io.WriteString(f.out, xml.Header) if err != nil { @@ -53,16 +53,16 @@ func junitTimeDuration(from, to time.Time) string { return strconv.FormatFloat(to.Sub(from).Seconds(), 'f', -1, 64) } -func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*feature) junitPackageSuite { +func (f *junitFormatter) buildJUNITPackageSuite() junitPackageSuite { suite := junitPackageSuite{ - Name: suiteName, - TestSuites: make([]*junitTestSuite, len(features)), - Time: junitTimeDuration(startedAt, timeNowFunc()), + Name: f.suiteName, + TestSuites: make([]*junitTestSuite, len(f.features)), + Time: junitTimeDuration(f.started, timeNowFunc()), } - sort.Sort(sortByName(features)) + sort.Sort(sortByName(f.features)) - for idx, feat := range features { + for idx, feat := range f.features { ts := junitTestSuite{ Name: feat.GherkinDocument.Feature.Name, Time: junitTimeDuration(feat.startedAt(), feat.finishedAt()), @@ -71,7 +71,8 @@ func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*f var testcaseNames = make(map[string]int) for _, pickleResult := range feat.pickleResults { - testcaseNames[pickleResult.name] = testcaseNames[pickleResult.name] + 1 + pickle := f.storage.mustGetPickle(pickleResult.pickleID) + testcaseNames[pickle.Name] = testcaseNames[pickle.Name] + 1 } var outlineNo = make(map[string]int) @@ -79,7 +80,9 @@ func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*f tc := junitTestCase{} tc.Time = junitTimeDuration(pickleResult.startedAt(), pickleResult.finishedAt()) - tc.Name = pickleResult.name + pickle := f.storage.mustGetPickle(pickleResult.pickleID) + + tc.Name = pickle.Name if testcaseNames[tc.Name] > 1 { outlineNo[tc.Name] = outlineNo[tc.Name] + 1 tc.Name += fmt.Sprintf(" #%d", outlineNo[tc.Name]) @@ -89,30 +92,32 @@ func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*f suite.Tests++ for _, stepResult := range pickleResult.stepResults { + pickleStep := f.storage.mustGetPickleStep(stepResult.pickleStepID) + switch stepResult.status { case passed: tc.Status = passed.String() case failed: tc.Status = failed.String() tc.Failure = &junitFailure{ - Message: fmt.Sprintf("Step %s: %s", stepResult.step.Text, stepResult.err), + Message: fmt.Sprintf("Step %s: %s", pickleStep.Text, stepResult.err), } case skipped: tc.Error = append(tc.Error, &junitError{ Type: "skipped", - Message: fmt.Sprintf("Step %s", stepResult.step.Text), + Message: fmt.Sprintf("Step %s", pickleStep.Text), }) case undefined: tc.Status = undefined.String() tc.Error = append(tc.Error, &junitError{ Type: "undefined", - Message: fmt.Sprintf("Step %s", stepResult.step.Text), + Message: fmt.Sprintf("Step %s", pickleStep.Text), }) case pending: tc.Status = pending.String() tc.Error = append(tc.Error, &junitError{ Type: "pending", - Message: fmt.Sprintf("Step %s: TODO: write pending definition", stepResult.step.Text), + Message: fmt.Sprintf("Step %s: TODO: write pending definition", pickleStep.Text), }) } } diff --git a/fmt_pretty.go b/fmt_pretty.go index 7123174..3cee435 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -61,7 +61,7 @@ func (f *pretty) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.printStep(f.lastStepResult()) + f.printStep(pickle, f.lastStepResult()) } func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -70,7 +70,7 @@ func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.printStep(f.lastStepResult()) + f.printStep(pickle, f.lastStepResult()) } func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -79,7 +79,7 @@ func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_Pickle f.lock.Lock() defer f.lock.Unlock() - f.printStep(f.lastStepResult()) + f.printStep(pickle, f.lastStepResult()) } func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) { @@ -88,7 +88,7 @@ func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleSte f.lock.Lock() defer f.lock.Unlock() - f.printStep(f.lastStepResult()) + f.printStep(pickle, f.lastStepResult()) } func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) { @@ -97,7 +97,7 @@ func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleSt f.lock.Lock() defer f.lock.Unlock() - f.printStep(f.lastStepResult()) + f.printStep(pickle, f.lastStepResult()) } func (f *pretty) Sync(cf ConcurrentFormatter) { @@ -196,13 +196,16 @@ func (f *pretty) Summary() { if len(failedStepResults) > 0 { fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n") for _, fail := range failedStepResults { - feature := f.findFeature(fail.owner.AstNodeIds[0]) + pickle := f.storage.mustGetPickle(fail.pickleID) + pickleStep := f.storage.mustGetPickleStep(fail.pickleStepID) - astScenario := f.findScenario(fail.owner.AstNodeIds[0]) - scenarioDesc := fmt.Sprintf("%s: %s", astScenario.Keyword, fail.owner.Name) + feature := f.findFeature(pickle.AstNodeIds[0]) - astStep := f.findStep(fail.step.AstNodeIds[0]) - stepDesc := strings.TrimSpace(astStep.Keyword) + " " + fail.step.Text + astScenario := f.findScenario(pickle.AstNodeIds[0]) + scenarioDesc := fmt.Sprintf("%s: %s", astScenario.Keyword, pickle.Name) + + 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)) @@ -240,7 +243,8 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in return } - for _, result := range f.lastFeature().lastPickleResult().stepResults { + lastPickleResult := f.lastFeature().lastPickleResult() + for _, result := range lastPickleResult.stepResults { // determine example row status switch { case result.status == failed: @@ -256,7 +260,8 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in // in first example, we need to print steps var text string - astStep := f.findStep(result.step.AstNodeIds[0]) + pickleStep := f.storage.mustGetPickleStep(result.pickleStepID) + astStep := f.findStep(pickleStep.AstNodeIds[0]) if result.def != nil { if m := outlinePlaceholderRegexp.FindAllStringIndex(astStep.Text, -1); len(m) > 0 { @@ -272,7 +277,9 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in text = cyan(astStep.Text) } - _, maxLength := f.scenarioLengths(result.owner.AstNodeIds[0]) + pickle := f.storage.mustGetPickle(lastPickleResult.pickleID) + + _, maxLength := f.scenarioLengths(pickle.AstNodeIds[0]) stepLength := f.lengthPickleStep(astStep.Keyword, astStep.Text) text += s(maxLength - stepLength) @@ -283,7 +290,7 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in // print the step outline fmt.Fprintln(f.out, s(f.indent*2)+cyan(strings.TrimSpace(astStep.Keyword))+" "+text) - if table := result.step.Argument.GetDataTable(); table != nil { + if table := pickleStep.Argument.GetDataTable(); table != nil { f.printTable(table, cyan) } @@ -326,10 +333,12 @@ func (f *pretty) printTableHeader(row *messages.GherkinDocument_Feature_TableRow f.printTableRow(row, max, cyan) } -func (f *pretty) printStep(result *stepResult) { - astBackground := f.findBackground(result.owner.AstNodeIds[0]) - astScenario := f.findScenario(result.owner.AstNodeIds[0]) - astStep := f.findStep(result.step.AstNodeIds[0]) +func (f *pretty) printStep(pickle *messages.Pickle, result *stepResult) { + pickleStep := f.storage.mustGetPickleStep(result.pickleStepID) + + astBackground := f.findBackground(pickle.AstNodeIds[0]) + astScenario := f.findScenario(pickle.AstNodeIds[0]) + astStep := f.findStep(pickleStep.AstNodeIds[0]) var backgroundSteps int if astBackground != nil { @@ -350,26 +359,26 @@ func (f *pretty) printStep(result *stepResult) { } if !astBackgroundStep && len(astScenario.Examples) > 0 { - f.printOutlineExample(result.owner, backgroundSteps) + f.printOutlineExample(pickle, backgroundSteps) return } - scenarioHeaderLength, maxLength := f.scenarioLengths(result.owner.AstNodeIds[0]) - stepLength := f.lengthPickleStep(astStep.Keyword, result.step.Text) + scenarioHeaderLength, maxLength := f.scenarioLengths(pickle.AstNodeIds[0]) + stepLength := f.lengthPickleStep(astStep.Keyword, pickleStep.Text) firstExecutedScenarioStep := len(f.lastFeature().lastPickleResult().stepResults) == 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()(result.step.Text) + text := s(f.indent*2) + result.status.clr()(strings.TrimSpace(astStep.Keyword)) + " " + result.status.clr()(pickleStep.Text) if result.def != nil { text += s(maxLength - stepLength + 1) text += blackb("# " + result.def.definitionID()) } fmt.Fprintln(f.out, text) - if table := result.step.Argument.GetDataTable(); table != nil { + if table := pickleStep.Argument.GetDataTable(); table != nil { f.printTable(table, cyan) } diff --git a/fmt_progress.go b/fmt_progress.go index 6e73692..98d9e95 100644 --- a/fmt_progress.go +++ b/fmt_progress.go @@ -41,13 +41,16 @@ func (f *progress) Summary() { var failedStepsOutput []string for _, sr := range f.findStepResults(failed) { if sr.status == failed { - sc := f.findScenario(sr.owner.AstNodeIds[0]) - scenarioDesc := fmt.Sprintf("%s: %s", sc.Keyword, sr.owner.Name) - scenarioLine := fmt.Sprintf("%s:%d", sr.owner.Uri, sc.Location.Line) + pickle := f.storage.mustGetPickle(sr.pickleID) + pickleStep := f.storage.mustGetPickleStep(sr.pickleStepID) - step := f.findStep(sr.step.AstNodeIds[0]) - stepDesc := strings.TrimSpace(step.Keyword) + " " + sr.step.Text - stepLine := fmt.Sprintf("%s:%d", sr.owner.Uri, step.Location.Line) + sc := f.findScenario(pickle.AstNodeIds[0]) + scenarioDesc := fmt.Sprintf("%s: %s", sc.Keyword, pickle.Name) + scenarioLine := fmt.Sprintf("%s:%d", pickle.Uri, sc.Location.Line) + + step := f.findStep(pickleStep.AstNodeIds[0]) + stepDesc := strings.TrimSpace(step.Keyword) + " " + pickleStep.Text + stepLine := fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line) failedStepsOutput = append( failedStepsOutput, diff --git a/fmt_progress_test.go b/fmt_progress_test.go index a673f54..789c6ea 100644 --- a/fmt_progress_test.go +++ b/fmt_progress_test.go @@ -40,6 +40,11 @@ func TestProgressFormatterWhenStepPanics(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.True(t, failed) @@ -54,6 +59,7 @@ func TestProgressFormatterWithPanicInMultistep(t *testing.T) { require.NoError(t, err) pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) + var buf bytes.Buffer w := colors.Uncolored(&buf) r := runner{ @@ -68,6 +74,11 @@ func TestProgressFormatterWithPanicInMultistep(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.True(t, failed) } @@ -93,6 +104,11 @@ func TestProgressFormatterMultistepTemplates(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) @@ -159,6 +175,11 @@ Feature: basic }, } + 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) } @@ -195,6 +216,11 @@ Feature: basic }, } + r.storage = newStorage() + for _, pickle := range pickles { + r.storage.mustInsertPickle(pickle) + } + failed := r.concurrent(1, func() Formatter { return progressFunc("progress", w) }) require.True(t, failed) diff --git a/go.mod b/go.mod index 1871721..acc9cd7 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.13 require ( github.com/cucumber/gherkin-go/v11 v11.0.0 github.com/cucumber/messages-go/v10 v10.0.3 + github.com/hashicorp/go-memdb v1.2.1 github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index 388bdf8..2aa69de 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,16 @@ github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/hashicorp/go-immutable-radix v1.2.0 h1:l6UW37iCXwZkZoAbEYnptSHVE/cQ5bOTPYG5W3vf9+8= +github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.2.1 h1:wI9btDjYUOJJHTCnRlAG/TkRyD/ij7meJMrLK9X31Cc= +github.com/hashicorp/go-memdb v1.2.1/go.mod h1:OSvLJ662Jim8hMM+gWGyhktyWk2xPCnWMc7DWIqtkGA= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= diff --git a/run.go b/run.go index 2b2ee55..5bf49ca 100644 --- a/run.go +++ b/run.go @@ -31,6 +31,8 @@ type runner struct { initializer initializer testSuiteInitializer testSuiteInitializer scenarioInitializer scenarioInitializer + + storage *storage } func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool) { @@ -42,10 +44,15 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool useFmtCopy = true } + if fmt, ok := r.fmt.(storageFormatter); ok { + fmt.setStorage(r.storage) + } + testSuiteContext := TestSuiteContext{} if r.testSuiteInitializer != nil { r.testSuiteInitializer(&testSuiteContext) } + r.fmt.TestRunStarted() // run before suite handlers @@ -73,6 +80,8 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool strict: r.strict, features: []*feature{feat}, } + + suite.fmt = r.fmt if useFmtCopy { fmtCopy = formatterFn() suite.fmt = fmtCopy @@ -83,8 +92,10 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool if dOk && sOk { concurrentDestFmt.Sync(concurrentSourceFmt) } - } else { - suite.fmt = r.fmt + } + + if fmt, ok := suite.fmt.(storageFormatter); ok { + fmt.setStorage(r.storage) } if r.initializer != nil { @@ -217,6 +228,13 @@ func runWithOptions(suite string, runner runner, opt Options) int { return exitOptionError } + runner.storage = newStorage() + for _, feat := range runner.features { + for _, pickle := range feat.pickles { + runner.storage.mustInsertPickle(pickle) + } + } + // user may have specified -1 option to create random seed runner.randomSeed = opt.Randomize if runner.randomSeed == -1 { diff --git a/run_test.go b/run_test.go index e458dc3..9ea3674 100644 --- a/run_test.go +++ b/run_test.go @@ -75,6 +75,11 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) { }, } + r.storage = newStorage() + for _, pickle := range pickles { + r.storage.mustInsertPickle(pickle) + } + failed := r.concurrent(1, func() Formatter { return progressFunc("progress", ioutil.Discard) }) require.False(t, failed) @@ -100,6 +105,11 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) { }, } + r.storage = newStorage() + for _, pickle := range pickles { + r.storage.mustInsertPickle(pickle) + } + failed := r.concurrent(1, func() Formatter { return progressFunc("progress", ioutil.Discard) }) require.False(t, failed) @@ -125,6 +135,11 @@ func TestShouldFailOnError(t *testing.T) { }, } + r.storage = newStorage() + for _, pickle := range pickles { + r.storage.mustInsertPickle(pickle) + } + failed := r.concurrent(1, func() Formatter { return progressFunc("progress", ioutil.Discard) }) require.True(t, failed) } diff --git a/storage.go b/storage.go new file mode 100644 index 0000000..e24039a --- /dev/null +++ b/storage.go @@ -0,0 +1,104 @@ +package godog + +import ( + "github.com/cucumber/messages-go/v10" + "github.com/hashicorp/go-memdb" +) + +const ( + writeMode bool = true + readMode bool = false + + tablePickle string = "pickle" + tablePickleIndexID string = "id" + + tablePickleStep string = "pickle_step" + tablePickleStepIndexID string = "id" +) + +type storage struct { + db *memdb.MemDB +} + +func newStorage() *storage { + // Create the DB schema + schema := memdb.DBSchema{ + Tables: map[string]*memdb.TableSchema{ + tablePickle: { + Name: tablePickle, + Indexes: map[string]*memdb.IndexSchema{ + tablePickleIndexID: { + Name: tablePickleIndexID, + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "Id"}, + }, + }, + }, + tablePickleStep: { + Name: tablePickleStep, + Indexes: map[string]*memdb.IndexSchema{ + tablePickleStepIndexID: { + Name: tablePickleStepIndexID, + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "Id"}, + }, + }, + }, + }, + } + + // Create a new data base + db, err := memdb.NewMemDB(&schema) + if err != nil { + panic(err) + } + + return &storage{db} +} + +func (s *storage) mustInsertPickle(p *messages.Pickle) (err error) { + txn := s.db.Txn(writeMode) + + if err = txn.Insert(tablePickle, p); err != nil { + panic(err) + } + + for _, step := range p.Steps { + if err = txn.Insert(tablePickleStep, step); err != nil { + panic(err) + } + } + + txn.Commit() + return +} + +func (s *storage) mustGetPickle(id string) *messages.Pickle { + txn := s.db.Txn(readMode) + defer txn.Abort() + + var v interface{} + v, err := txn.First(tablePickle, tablePickleIndexID, id) + if err != nil { + panic(err) + } else if v == nil { + panic("Couldn't find pickle with ID: " + id) + } + + return v.(*messages.Pickle) +} + +func (s *storage) mustGetPickleStep(id string) *messages.Pickle_PickleStep { + txn := s.db.Txn(readMode) + defer txn.Abort() + + var v interface{} + v, err := txn.First(tablePickleStep, tablePickleStepIndexID, id) + if err != nil { + panic(err) + } else if v == nil { + panic("Couldn't find pickle step with ID: " + id) + } + + return v.(*messages.Pickle_PickleStep) +} diff --git a/suite.go b/suite.go index db29cb6..4462a32 100644 --- a/suite.go +++ b/suite.go @@ -129,8 +129,8 @@ func (s sortByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Featur func (s sortByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } type pickleResult struct { - name string - astNodeIDs []string + pickleID string + time time.Time stepResults []*stepResult } diff --git a/suite_context.go b/suite_context.go index 1e28894..fffd3c6 100644 --- a/suite_context.go +++ b/suite_context.go @@ -183,7 +183,16 @@ func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error { applyTagFilter(tags, feat) } - s.testedSuite.fmt = newBaseFmt("godog", &s.out) + st := newStorage() + for _, feat := range s.testedSuite.features { + for _, pickle := range feat.pickles { + st.mustInsertPickle(pickle) + } + } + + fmt := newBaseFmt("godog", &s.out) + fmt.setStorage(st) + s.testedSuite.fmt = fmt s.testedSuite.fmt.TestRunStarted() s.testedSuite.run() @@ -193,15 +202,25 @@ func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error { } func (s *suiteContext) iRunFeatureSuiteWithFormatter(name string) error { + if err := s.parseFeatures(); err != nil { + return err + } + f := FindFmt(name) if f == nil { return fmt.Errorf(`formatter "%s" is not available`, name) } - s.testedSuite.fmt = f("godog", colors.Uncolored(&s.out)) + st := newStorage() + for _, feat := range s.testedSuite.features { + for _, pickle := range feat.pickles { + st.mustInsertPickle(pickle) + } + } - if err := s.parseFeatures(); err != nil { - return err + s.testedSuite.fmt = f("godog", colors.Uncolored(&s.out)) + if fmt, ok := s.testedSuite.fmt.(storageFormatter); ok { + fmt.setStorage(st) } s.testedSuite.fmt.TestRunStarted() @@ -276,23 +295,28 @@ func (s *suiteContext) followingStepsShouldHave(status string, steps *DocString) switch status { case "passed": for _, st := range f.findStepResults(passed) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "failed": for _, st := range f.findStepResults(failed) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "skipped": for _, st := range f.findStepResults(skipped) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "undefined": for _, st := range f.findStepResults(undefined) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "pending": for _, st := range f.findStepResults(pending) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } default: return fmt.Errorf("unexpected step status wanted: %s", status) diff --git a/suite_context_test.go b/suite_context_test.go index 2d18dda..db4a8ab 100644 --- a/suite_context_test.go +++ b/suite_context_test.go @@ -145,7 +145,16 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithTags(tags string) error { applyTagFilter(tags, feat) } - tc.testedSuite.fmt = newBaseFmt("godog", &tc.out) + st := newStorage() + for _, feat := range tc.testedSuite.features { + for _, pickle := range feat.pickles { + st.mustInsertPickle(pickle) + } + } + + fmt := newBaseFmt("godog", &tc.out) + fmt.setStorage(st) + tc.testedSuite.fmt = fmt tc.testedSuite.fmt.TestRunStarted() tc.testedSuite.run() @@ -155,15 +164,25 @@ func (tc *godogFeaturesScenario) iRunFeatureSuiteWithTags(tags string) error { } func (tc *godogFeaturesScenario) iRunFeatureSuiteWithFormatter(name string) error { + if err := tc.parseFeatures(); err != nil { + return err + } + f := FindFmt(name) if f == nil { return fmt.Errorf(`formatter "%s" is not available`, name) } - tc.testedSuite.fmt = f("godog", colors.Uncolored(&tc.out)) + st := newStorage() + for _, feat := range tc.testedSuite.features { + for _, pickle := range feat.pickles { + st.mustInsertPickle(pickle) + } + } - if err := tc.parseFeatures(); err != nil { - return err + tc.testedSuite.fmt = f("godog", colors.Uncolored(&tc.out)) + if fmt, ok := tc.testedSuite.fmt.(storageFormatter); ok { + fmt.setStorage(st) } tc.testedSuite.fmt.TestRunStarted() @@ -238,23 +257,28 @@ func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps * switch status { case "passed": for _, st := range f.findStepResults(passed) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "failed": for _, st := range f.findStepResults(failed) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "skipped": for _, st := range f.findStepResults(skipped) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "undefined": for _, st := range f.findStepResults(undefined) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } case "pending": for _, st := range f.findStepResults(pending) { - actual = append(actual, st.step.Text) + pickleStep := f.storage.mustGetPickleStep(st.pickleStepID) + actual = append(actual, pickleStep.Text) } default: return fmt.Errorf("unexpected step status wanted: %s", status)