package godog import ( "fmt" "io" "time" "strings" "github.com/DATA-DOG/godog/gherkin" "encoding/json" ) const cukeurl = "https://www.relishapp.com/cucumber/cucumber/docs/formatters/json-output-formatter" func init() { Format("cucumber", fmt.Sprintf("Produces cucumber JSON stream, based on spec @: %s.", cukeurl), cucumberFunc) } func cucumberFunc(suite string, out io.Writer) Formatter { formatter := &cukefmt{ basefmt: basefmt{ started: time.Now(), indent: 2, out: out, }, } return formatter } // Replace spaces with - func makeId(name string) string { return strings.Replace(strings.ToLower(name), " ", "-", -1) } type cukeTag struct { Name string `json:"name"` Line int `json:"line"` } type cukeResult struct { Status string `json:"status"` Duration int `json:"duration"` Error string `json:"error_message,omitempty"` } type cukeMatch struct { Location string `json:"location"` } type cukeStep struct { Keyword string `json:"keyword"` Name string `json:"name"` Line int `json:"line"` Match cukeMatch `json:"match"` Result cukeResult `json:"result"` } type cukeElement struct { Keyword string `json:"keyword"` Id string `json:"id"` Name string `json:"name"` Line int `json:"line"` Description string `json:"description"` Tags []cukeTag `json:"tags"` Type string `json:"type"` Steps []cukeStep `json:"steps"` } type cukeFeatureJson struct { Uri string `json:"uri"` Id string `json:"id"` Keyword string `json:"keyword"` Name string `json:"name"` Line int `json:"line"` Description string `json:"description"` Tags []cukeTag `json:"tags"` Elements []cukeElement `json:"elements"` } 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 } func (f *cukefmt) Node(n interface{}) { f.basefmt.Node(n) switch t := n.(type) { case *gherkin.Examples: fmt.Fprintln("Examples") saveElement := f.curElement f.curElement.Id = f.curElement.Id + ";" + makeId(t.Name) f.curFeature.Elements = append(f.curFeature.Elements, f.curElement) f.curElement = saveElement case *gherkin.ScenarioOutline: fmt.Fprintln("Outline") //f.curFeature.Elements = append(f.curFeature.Elements, cukeElement{}) //f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements) - 1] f.curElement=cukeElement{} f.curElement.Name = t.Name f.curElement.Line = t.Location.Line f.curElement.Description = t.Description 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)) for idx, element := range t.Tags { f.curElement.Tags[idx].Line = element.Location.Line f.curElement.Tags[idx].Name = element.Name } case *gherkin.Scenario: fmt.Fprintln("Scenario") f.curElement = cukeElement{} f.curElement.Name = t.Name f.curElement.Line = t.Location.Line f.curElement.Description = t.Description 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)) for idx, element := range t.Tags { f.curElement.Tags[idx].Line = element.Location.Line f.curElement.Tags[idx].Name = element.Name } f.curFeature.Elements = append(f.curFeature.Elements, f.curElement) case *gherkin.TableRow: lastElement := &f.curFeature.Elements[len(f.curFeature.Elements) - 1] lastElement.Id = lastElement.Id + ";cnt" } } func (f *cukefmt) Feature(ft *gherkin.Feature, p string, c []byte) { f.basefmt.Feature(ft, p, c) f.path = p f.id = makeId(ft.Name) f.results = append(f.results, cukeFeatureJson{}) f.curFeature = &f.results[len(f.results) - 1] f.curFeature.Uri = p f.curFeature.Name = ft.Name f.curFeature.Keyword = ft.Keyword f.curFeature.Line = ft.Location.Line f.curFeature.Description = ft.Description f.curFeature.Id = f.id f.curFeature.Tags = make([]cukeTag, len(ft.Tags)) for idx, element := range ft.Tags { f.curFeature.Tags[idx].Line = element.Location.Line f.curFeature.Tags[idx].Name = element.Name } } func (f *cukefmt) Summary() { dat, err := json.MarshalIndent(f.results, "", "\t") if err != nil { panic(err) } fmt.Fprintf(f.out, "%s\n", string(dat)) } func (f *cukefmt) step(res *stepResult) { // determine if test case has finished switch t:= f.owner.(type) { case *gherkin.TableRow: f.curStep.Line = t.Location.Line f.curStep.Result.Status = res.typ.String() if res.err != nil { f.curStep.Result.Error = res.err.Error() } case *gherkin.Scenario: f.curStep.Result.Status = res.typ.String() if res.err != nil { f.curStep.Result.Error = res.err.Error() } } } func (f *cukefmt) Defined(step *gherkin.Step, def *StepDef) { fmt.Fprintf(f.out, "Defined: step:%v stepDef:%v\n", step, def) f.curElement.Steps = append(f.curElement.Steps, cukeStep{}) f.curStep = &f.curElement.Steps[len(f.curElement.Steps) - 1] if def != nil { if def.args != nil { fmt.Fprintf(f.out, "Argument: %v\n", def.args) } } f.curStep.Name = step.Text f.curStep.Line = step.Location.Line f.curStep.Keyword = step.Keyword if def != nil { f.curStep.Match.Location = strings.Split(def.definitionID(), " ")[0] } } 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]) } func (f *cukefmt) Skipped(step *gherkin.Step) { f.basefmt.Skipped(step) f.step(f.skipped[len(f.skipped) - 1]) } func (f *cukefmt) Undefined(step *gherkin.Step) { f.basefmt.Undefined(step) f.stat = undefined f.step(f.undefined[len(f.undefined) - 1]) } 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]) } 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]) }