From 819ce03090008ab1dec7dcc3e0997b6dc4f0a304 Mon Sep 17 00:00:00 2001 From: "Stettler, Robert" Date: Thu, 2 Mar 2017 10:50:30 -0500 Subject: [PATCH] Reformatting. --- features/formatter/cucumber.feature | 551 ++++++++++++++++++++++++++++ fmt_cucumber.go | 80 ++-- suite_test.go | 5 +- 3 files changed, 594 insertions(+), 42 deletions(-) create mode 100644 features/formatter/cucumber.feature diff --git a/features/formatter/cucumber.feature b/features/formatter/cucumber.feature new file mode 100644 index 0000000..78a499e --- /dev/null +++ b/features/formatter/cucumber.feature @@ -0,0 +1,551 @@ +Feature: cucumber json formatter + In order to support tools that import cucumber json output + I need to be able to support cucumber json formatted output + + Scenario: Support of Feature Plus Scenario Node + Given a feature "features/simple.feature" file: + """ + Feature: simple feature + simple feature description + Scenario: simple scenario + simple scenario description + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ application/json + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple feature description", + "line": 1, + "elements": [ + { + "id": "simple-feature;simple-scenario", + "keyword": "Scenario", + "name": "simple scenario", + "description": " simple scenario description", + "line": 3, + "type": "scenario" + } + ] + } + ] + """ + + Scenario: Support of Feature Plus Scenario Node With Tags + Given a feature "features/simple.feature" file: + """ + @TAG1 + Feature: simple feature + simple feature description + @TAG2 @TAG3 + Scenario: simple scenario + simple scenario description + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ application/json + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple feature description", + "line": 2, + "tags": [ + { + "name": "@TAG1", + "line": 1 + } + ], + "elements": [ + { + "id": "simple-feature;simple-scenario", + "keyword": "Scenario", + "name": "simple scenario", + "description": " simple scenario description", + "line": 5, + "type": "scenario", + "tags": [ + { + "name": "@TAG1", + "line": 1 + }, + { + "name": "@TAG2", + "line": 4 + }, + { + "name": "@TAG3", + "line": 4 + } + ] + } + ] + } + ] + """ + Scenario: Support of Feature Plus Scenario Outline + Given a feature "features/simple.feature" file: + """ + Feature: simple feature + simple feature description + + Scenario Outline: simple scenario + simple scenario description + + Examples: simple examples + | status | + | pass | + | fail | + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple feature description", + "line": 1, + "elements": [ + { + "id": "simple-feature;simple-scenario;simple-examples;2", + "keyword": "Scenario Outline", + "name": "simple scenario", + "description": " simple scenario description", + "line": 9, + "type": "scenario" + }, + { + "id": "simple-feature;simple-scenario;simple-examples;3", + "keyword": "Scenario Outline", + "name": "simple scenario", + "description": " simple scenario description", + "line": 10, + "type": "scenario" + } + ] + } + ] + """ + + Scenario: Support of Feature Plus Scenario Outline With Tags + Given a feature "features/simple.feature" file: + """ + @TAG1 + Feature: simple feature + simple feature description + + @TAG2 + Scenario Outline: simple scenario + simple scenario description + + @TAG3 + Examples: simple examples + | status | + | pass | + | fail | + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple feature description", + "line": 2, + "tags": [ + { + "name": "@TAG1", + "line": 1 + } + ], + "elements": [ + { + "id": "simple-feature;simple-scenario;simple-examples;2", + "keyword": "Scenario Outline", + "name": "simple scenario", + "description": " simple scenario description", + "line": 12, + "type": "scenario", + "tags": [ + { + "name": "@TAG1", + "line": 1 + }, + { + "name": "@TAG2", + "line": 5 + }, + { + "name": "@TAG3", + "line": 9 + } + ] + }, + { + "id": "simple-feature;simple-scenario;simple-examples;3", + "keyword": "Scenario Outline", + "name": "simple scenario", + "description": " simple scenario description", + "line": 13, + "type": "scenario", + "tags": [ + { + "name": "@TAG1", + "line": 1 + }, + { + "name": "@TAG2", + "line": 5 + }, + { + "name": "@TAG3", + "line": 9 + } + ] + } + ] + } + ] + """ + Scenario: Support of Feature Plus Scenario With Steps + Given a feature "features/simple.feature" file: + """ + Feature: simple feature + simple feature description + + Scenario: simple scenario + simple scenario description + + Given passing step + Then a failing step + + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple feature description", + "line": 1, + "elements": [ + { + "id": "simple-feature;simple-scenario", + "keyword": "Scenario", + "name": "simple scenario", + "description": " simple scenario description", + "line": 4, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "passing step", + "line": 7, + "match": { + "location": "STEP_ID" + }, + "result": { + "status": "passed", + "duration": -1 + } + }, + { + "keyword": "Then ", + "name": "a failing step", + "line": 8, + "match": { + "location": "STEP_ID" + }, + "result": { + "status": "failed", + "error_message": "intentional failure", + "duration": -1 + } + } + ] + } + ] + } + ] + """ + Scenario: Support of Feature Plus Scenario Outline With Steps + Given a feature "features/simple.feature" file: + """ + Feature: simple feature + simple feature description + + Scenario Outline: simple scenario + simple scenario description + + Given step + + Examples: simple examples + | status | + | passing | + | failing | + + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple feature description", + "line": 1, + "elements": [ + { + "id": "simple-feature;simple-scenario;simple-examples;2", + "keyword": "Scenario Outline", + "name": "simple scenario", + "description": " simple scenario description", + "line": 11, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "passing step", + "line": 11, + "match": { + "location": "STEP_ID" + }, + "result": { + "status": "passed", + "duration": -1 + } + } + ] + }, + { + "id": "simple-feature;simple-scenario;simple-examples;3", + "keyword": "Scenario Outline", + "name": "simple scenario", + "description": " simple scenario description", + "line": 12, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "failing step", + "line": 12, + "match": { + "location": "STEP_ID" + }, + "result": { + "status": "failed", + "error_message": "intentional failure", + "duration": -1 + } + } + ] + } + ] + } + ] + """ + + # Currently godog only supports comments on Feature and not + # scenario and steps. + Scenario: Support of Comments + Given a feature "features/simple.feature" file: + """ + #Feature comment + Feature: simple feature + simple description + + Scenario: simple scenario + simple feature description + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple description", + "line": 2, + "comments": [ + { + "value": "#Feature comment", + "line": 1 + } + ], + "elements": [ + { + "id": "simple-feature;simple-scenario", + "keyword": "Scenario", + "name": "simple scenario", + "description": " simple feature description", + "line": 5, + "type": "scenario" + } + ] + } + ] + """ + Scenario: Support of Docstrings + Given a feature "features/simple.feature" file: + """ + Feature: simple feature + simple description + + Scenario: simple scenario + simple feature description + + Given passing step + \"\"\" content type + step doc string + \"\"\" + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple description", + "line": 1, + "elements": [ + { + "id": "simple-feature;simple-scenario", + "keyword": "Scenario", + "name": "simple scenario", + "description": " simple feature description", + "line": 4, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "passing step", + "line": 7, + "doc_string": { + "value": "step doc string", + "content_type": "content type", + "line": 8 + }, + "match": { + "location": "STEP_ID" + }, + "result": { + "status": "passed", + "duration": -1 + } + } + ] + } + ] + } + ] + """ + Scenario: Support of Undefined, Pending and Skipped status + Given a feature "features/simple.feature" file: + """ + Feature: simple feature + simple feature description + + Scenario: simple scenario + simple scenario description + + Given passing step + And pending step + And undefined + And passing step + + """ + When I run feature suite with formatter "cucumber" + Then the rendered json will be as follows: + """ + [ + { + "uri": "features/simple.feature", + "id": "simple-feature", + "keyword": "Feature", + "name": "simple feature", + "description": " simple feature description", + "line": 1, + "elements": [ + { + "id": "simple-feature;simple-scenario", + "keyword": "Scenario", + "name": "simple scenario", + "description": " simple scenario description", + "line": 4, + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "passing step", + "line": 7, + "match": { + "location": "STEP_ID" + }, + "result": { + "status": "passed", + "duration": 397087 + } + }, + { + "keyword": "And ", + "name": "pending step", + "line": 8, + "match": { + "location": "FEATURE_PATH features/simple.feature:8" + }, + "result": { + "status": "pending" + } + }, + { + "keyword": "And ", + "name": "undefined", + "line": 9, + "match": { + "location": "FEATURE_PATH features/simple.feature:9" + }, + "result": { + "status": "undefined" + } + }, + { + "keyword": "And ", + "name": "passing step", + "line": 10, + "match": { + "location": "STEP_ID" + }, + "result": { + "status": "skipped" + } + } + ] + } + ] + } + ] + """ + + diff --git a/fmt_cucumber.go b/fmt_cucumber.go index fbf15c4..52a00cc 100644 --- a/fmt_cucumber.go +++ b/fmt_cucumber.go @@ -64,7 +64,7 @@ type cukeTag struct { type cukeResult struct { Status string `json:"status"` Error string `json:"error_message,omitempty"` - Duration *int `json:"duration,omitempty"` + Duration *int `json:"duration,omitempty"` } type cukeMatch struct { @@ -72,12 +72,12 @@ type cukeMatch struct { } type cukeStep struct { - Keyword string `json:"keyword"` - Name string `json:"name"` - Line int `json:"line"` + Keyword string `json:"keyword"` + Name string `json:"name"` + Line int `json:"line"` Docstring *cukeDocstring `json:"doc_string,omitempty"` - Match cukeMatch `json:"match"` - Result cukeResult `json:"result"` + Match cukeMatch `json:"match"` + Result cukeResult `json:"result"` } type cukeElement struct { @@ -106,25 +106,25 @@ type cukeFeatureJSON struct { type cukefmt struct { basefmt - // currently running feature path, to be part of id. - // this is sadly not passed by gherkin nodes. - // it restricts this formatter to run only in synchronous single - // threaded execution. Unless running a copy of formatter for each feature - path string - stat stepType // last step status, before skipped - outlineSteps int // number of current outline scenario steps - ID string // current test id. - results []cukeFeatureJSON // structure that represent cuke results - curStep *cukeStep // track the current step - curElement *cukeElement // track the current element - curFeature *cukeFeatureJSON // track the current feature - curOutline cukeElement // Each example show up as an outline element but the outline is parsed only once - // so I need to keep track of the current outline - curRow int // current row of the example table as it is being processed. - curExampleTags []cukeTag // temporary storage for tags associate with the current example table. - startTime time.Time // used to time duration of the step execution - curExampleName string // Due to the fact that examples are parsed once and then iterated over for each result then we need to keep track - // of the example name inorder to build id fields. + // currently running feature path, to be part of id. + // this is sadly not passed by gherkin nodes. + // it restricts this formatter to run only in synchronous single + // threaded execution. Unless running a copy of formatter for each feature + path string + stat stepType // last step status, before skipped + outlineSteps int // number of current outline scenario steps + ID string // current test id. + results []cukeFeatureJSON // structure that represent cuke results + curStep *cukeStep // track the current step + curElement *cukeElement // track the current element + curFeature *cukeFeatureJSON // track the current feature + curOutline cukeElement // Each example show up as an outline element but the outline is parsed only once + // so I need to keep track of the current outline + curRow int // current row of the example table as it is being processed. + curExampleTags []cukeTag // temporary storage for tags associate with the current example table. + startTime time.Time // used to time duration of the step execution + curExampleName string // Due to the fact that examples are parsed once and then iterated over for each result then we need to keep track + // of the example name inorder to build id fields. } func (f *cukefmt) Node(n interface{}) { @@ -157,7 +157,7 @@ func (f *cukefmt) Node(n interface{}) { f.curOutline.Keyword = t.Keyword f.curOutline.ID = f.curFeature.ID + ";" + makeID(t.Name) f.curOutline.Type = "scenario" - f.curOutline.Tags = make([]cukeTag, len(t.Tags) + len(f.curFeature.Tags)) + f.curOutline.Tags = make([]cukeTag, len(t.Tags)+len(f.curFeature.Tags)) // apply feature level tags if len(f.curOutline.Tags) > 0 { @@ -165,15 +165,15 @@ func (f *cukefmt) Node(n interface{}) { // apply outline level tags. for idx, element := range t.Tags { - f.curOutline.Tags[idx + len(f.curFeature.Tags)].Line = element.Location.Line - f.curOutline.Tags[idx + len(f.curFeature.Tags)].Name = element.Name + f.curOutline.Tags[idx+len(f.curFeature.Tags)].Line = element.Location.Line + f.curOutline.Tags[idx+len(f.curFeature.Tags)].Name = element.Name } } // This scenario adds the element to the output immediately. case *gherkin.Scenario: f.curFeature.Elements = append(f.curFeature.Elements, cukeElement{}) - f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements) - 1] + f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements)-1] f.curElement.Name = t.Name f.curElement.Line = t.Location.Line @@ -181,7 +181,7 @@ func (f *cukefmt) Node(n interface{}) { f.curElement.Keyword = t.Keyword f.curElement.ID = f.curFeature.ID + ";" + makeID(t.Name) f.curElement.Type = "scenario" - f.curElement.Tags = make([]cukeTag, len(t.Tags) + len(f.curFeature.Tags)) + f.curElement.Tags = make([]cukeTag, len(t.Tags)+len(f.curFeature.Tags)) if len(f.curElement.Tags) > 0 { // apply feature level tags @@ -189,8 +189,8 @@ func (f *cukefmt) Node(n interface{}) { // apply scenario level tags. for idx, element := range t.Tags { - f.curElement.Tags[idx + len(f.curFeature.Tags)].Line = element.Location.Line - f.curElement.Tags[idx + len(f.curFeature.Tags)].Name = element.Name + f.curElement.Tags[idx+len(f.curFeature.Tags)].Line = element.Location.Line + f.curElement.Tags[idx+len(f.curFeature.Tags)].Name = element.Name } } @@ -202,7 +202,7 @@ func (f *cukefmt) Node(n interface{}) { tmpElem.ID = tmpElem.ID + ";" + f.curExampleName + ";" + strconv.Itoa(f.curRow) f.curRow++ f.curFeature.Elements = append(f.curFeature.Elements, tmpElem) - f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements) - 1] + f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements)-1] // copy in example level tags. f.curElement.Tags = append(f.curElement.Tags, f.curExampleTags...) @@ -218,7 +218,7 @@ func (f *cukefmt) Feature(ft *gherkin.Feature, p string, c []byte) { f.ID = makeID(ft.Name) f.results = append(f.results, cukeFeatureJSON{}) - f.curFeature = &f.results[len(f.results) - 1] + f.curFeature = &f.results[len(f.results)-1] f.curFeature.URI = p f.curFeature.Name = ft.Name f.curFeature.Keyword = ft.Keyword @@ -274,7 +274,7 @@ func (f *cukefmt) Defined(step *gherkin.Step, def *StepDef) { f.startTime = time.Now() // start timing the step f.curElement.Steps = append(f.curElement.Steps, cukeStep{}) - f.curStep = &f.curElement.Steps[len(f.curElement.Steps) - 1] + f.curStep = &f.curElement.Steps[len(f.curElement.Steps)-1] f.curStep.Name = step.Text f.curStep.Line = step.Location.Line @@ -295,12 +295,12 @@ func (f *cukefmt) Defined(step *gherkin.Step, def *StepDef) { func (f *cukefmt) Passed(step *gherkin.Step, match *StepDef) { f.basefmt.Passed(step, match) f.stat = passed - f.step(f.passed[len(f.passed) - 1]) + f.step(f.passed[len(f.passed)-1]) } func (f *cukefmt) Skipped(step *gherkin.Step) { f.basefmt.Skipped(step) - f.step(f.skipped[len(f.skipped) - 1]) + f.step(f.skipped[len(f.skipped)-1]) // no duration reported for skipped. f.curStep.Result.Duration = nil @@ -309,7 +309,7 @@ func (f *cukefmt) Skipped(step *gherkin.Step) { func (f *cukefmt) Undefined(step *gherkin.Step) { f.basefmt.Undefined(step) f.stat = undefined - f.step(f.undefined[len(f.undefined) - 1]) + f.step(f.undefined[len(f.undefined)-1]) // the location for undefined is the feature file location not the step file. f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, step.Location.Line) @@ -319,13 +319,13 @@ func (f *cukefmt) Undefined(step *gherkin.Step) { func (f *cukefmt) Failed(step *gherkin.Step, match *StepDef, err error) { f.basefmt.Failed(step, match, err) f.stat = failed - f.step(f.failed[len(f.failed) - 1]) + f.step(f.failed[len(f.failed)-1]) } func (f *cukefmt) Pending(step *gherkin.Step, match *StepDef) { f.stat = pending f.basefmt.Pending(step, match) - f.step(f.pending[len(f.pending) - 1]) + f.step(f.pending[len(f.pending)-1]) // the location for pending is the feature file location not the step file. f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, step.Location.Line) diff --git a/suite_test.go b/suite_test.go index a52e457..e8c3f9e 100644 --- a/suite_test.go +++ b/suite_test.go @@ -366,7 +366,7 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(expected string) err return fmt.Errorf("before scenario event was never triggered or listened") } - return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, expected, `"` + strings.Join(found, `", "`) + `"`) + return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, expected, `"`+strings.Join(found, `", "`)+`"`) } func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.DataTable) error { @@ -503,6 +503,7 @@ func (s *suiteContext) mapCompare(expected map[string]interface{}, actual map[st return nil } + /* Due to specialize matching logic to ignore exact matches on the "location" and "duration" fields. It was necessary to create this compare function to validate the values of the map. @@ -531,4 +532,4 @@ func (s *suiteContext) mapCompareStructure(expected map[string]interface{}, actu } return nil -} \ No newline at end of file +}