Updated lang.feature with the additional cucumber.feature Updated load.feature with the additional cucumber.feature Updated events.feature to account for ripple effect of updating lang and load Updated run.go to indicate that cucumber formatter does not support concurrent. Updated suite_test.go to handle one new testing step.
		
			
				
	
	
		
			319 строки
		
	
	
	
		
			10 КиБ
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			319 строки
		
	
	
	
		
			10 КиБ
		
	
	
	
		
			Go
		
	
	
	
	
	
package godog
 | 
						|
/*
 | 
						|
   The specification for the formatting originated from https://www.relishapp.com/cucumber/cucumber/docs/formatters/json-output-formatter.
 | 
						|
   I found that documentation was misleading or out dated.  To validate formatting I create a ruby cucumber test harness and ran the
 | 
						|
   same feature files through godog and the ruby cucumber.
 | 
						|
 | 
						|
   The docstrings in the cucumber.feature represent the cucumber output for those same feature definitions.
 | 
						|
 | 
						|
   I did note that comments in ruby could be at just about any level in particular Feature, Scenario and Step.  In godog I
 | 
						|
   could only find comments under the Feature data structure.
 | 
						|
 */
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"time"
 | 
						|
	"strings"
 | 
						|
	"encoding/json"
 | 
						|
	"github.com/DATA-DOG/godog/gherkin"
 | 
						|
	"strconv"
 | 
						|
)
 | 
						|
 | 
						|
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 - This function is used to create the "id" fields of the cucumber output.
 | 
						|
func makeId(name string) string {
 | 
						|
	return strings.Replace(strings.ToLower(name), " ", "-", -1)
 | 
						|
}
 | 
						|
 | 
						|
// The sequence of type structs are used to marshall the json object.
 | 
						|
type cukeComment struct {
 | 
						|
	Value string `json:"value"`
 | 
						|
	Line  int    `json:"line"`
 | 
						|
}
 | 
						|
 | 
						|
type cukeDocstring struct {
 | 
						|
	Value string `json:"value"`
 | 
						|
	ContentType string `json:"content_type"`
 | 
						|
	Line  int    `json:"line"`
 | 
						|
}
 | 
						|
 | 
						|
type cukeTag struct {
 | 
						|
	Name string `json:"name"`
 | 
						|
	Line int    `json:"line"`
 | 
						|
}
 | 
						|
 | 
						|
type cukeResult struct {
 | 
						|
	Status   string `json:"status"`
 | 
						|
	Error    string `json:"error_message,omitempty"`
 | 
						|
	Duration int    `json:"duration"`
 | 
						|
}
 | 
						|
 | 
						|
type cukeMatch struct {
 | 
						|
	Location string `json:"location"`
 | 
						|
}
 | 
						|
 | 
						|
type cukeStep struct {
 | 
						|
	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"`
 | 
						|
}
 | 
						|
 | 
						|
type cukeElement struct {
 | 
						|
	Id          string     `json:"id"`
 | 
						|
	Keyword     string     `json:"keyword"`
 | 
						|
	Name        string     `json:"name"`
 | 
						|
	Description string     `json:"description"`
 | 
						|
	Line        int        `json:"line"`
 | 
						|
	Type        string     `json:"type"`
 | 
						|
	Tags        []cukeTag  `json:"tags,omitempty"`
 | 
						|
	Steps       []cukeStep `json:"steps,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type cukeFeatureJson struct {
 | 
						|
	Uri         string        `json:"uri"`
 | 
						|
	Id          string        `json:"id"`
 | 
						|
	Keyword     string        `json:"keyword"`
 | 
						|
	Name        string        `json:"name"`
 | 
						|
	Description string        `json:"description"`
 | 
						|
	Line        int           `json:"line"`
 | 
						|
	Comments    []cukeComment `json:"comments,omitempty"`
 | 
						|
	Tags        []cukeTag     `json:"tags,omitempty"`
 | 
						|
	Elements    []cukeElement `json:"elements,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
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.
 | 
						|
}
 | 
						|
 | 
						|
func (f *cukefmt) Node(n interface{}) {
 | 
						|
	f.basefmt.Node(n)
 | 
						|
 | 
						|
	switch t := n.(type) {
 | 
						|
 | 
						|
	// When the example definition is seen we just need track the id and
 | 
						|
	// append the name associated with the example as part of the id.
 | 
						|
	case *gherkin.Examples:
 | 
						|
 | 
						|
		f.curExampleName = makeId(t.Name)
 | 
						|
		f.curRow = 2 // there can be more than one example set per outline so reset row count.
 | 
						|
		// cucumber counts the header row as an example when creating the id.
 | 
						|
 | 
						|
		// store any example level tags in a  temp location.
 | 
						|
		f.curExampleTags = make([]cukeTag, len(t.Tags))
 | 
						|
		for idx, element := range t.Tags {
 | 
						|
			f.curExampleTags[idx].Line = element.Location.Line
 | 
						|
			f.curExampleTags[idx].Name = element.Name
 | 
						|
		}
 | 
						|
 | 
						|
	// The outline node creates a placeholder and the actual element is added as each TableRow is processed.
 | 
						|
	case *gherkin.ScenarioOutline:
 | 
						|
 | 
						|
		f.curOutline = cukeElement{}
 | 
						|
		f.curOutline.Name = t.Name
 | 
						|
		f.curOutline.Line = t.Location.Line
 | 
						|
		f.curOutline.Description = t.Description
 | 
						|
		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))
 | 
						|
 | 
						|
		// apply feature level tags
 | 
						|
		if len(f.curOutline.Tags) > 0 {
 | 
						|
			copy(f.curOutline.Tags, f.curFeature.Tags)
 | 
						|
 | 
						|
			// 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
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	// 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.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)+len(f.curFeature.Tags))
 | 
						|
 | 
						|
		if len(f.curElement.Tags) > 0 {
 | 
						|
			// apply feature level tags
 | 
						|
			copy(f.curElement.Tags, f.curFeature.Tags)
 | 
						|
 | 
						|
			// 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
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
	// This is an outline scenario and the element is added to the output as
 | 
						|
	// the TableRows are encountered.
 | 
						|
	case *gherkin.TableRow:
 | 
						|
		tmpElem := f.curOutline
 | 
						|
		tmpElem.Line = t.Location.Line
 | 
						|
		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]
 | 
						|
 | 
						|
		// copy in example level tags.
 | 
						|
		f.curElement.Tags = append(f.curElement.Tags, f.curExampleTags...)
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
	}
 | 
						|
 | 
						|
	f.curFeature.Comments = make([]cukeComment, len(ft.Comments))
 | 
						|
	for idx, comment := range ft.Comments {
 | 
						|
		f.curFeature.Comments[idx].Value = strings.TrimSpace(comment.Text)
 | 
						|
		f.curFeature.Comments[idx].Line = comment.Location.Line
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func (f *cukefmt) Summary() {
 | 
						|
	dat, err := json.MarshalIndent(f.results, "", "    ")
 | 
						|
	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.Result.Duration = int(time.Since(f.startTime).Nanoseconds())
 | 
						|
		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.Duration = int(time.Since(f.startTime).Nanoseconds())
 | 
						|
		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) {
 | 
						|
 | 
						|
	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.Name = step.Text
 | 
						|
	f.curStep.Line = step.Location.Line
 | 
						|
	f.curStep.Keyword = step.Keyword
 | 
						|
 | 
						|
	if _, ok := step.Argument.(*gherkin.DocString); ok {
 | 
						|
		f.curStep.Docstring.ContentType = strings.TrimSpace(step.Argument.(*gherkin.DocString).ContentType)
 | 
						|
		f.curStep.Docstring.Line = step.Argument.(*gherkin.DocString).Location.Line
 | 
						|
		f.curStep.Docstring.Value = step.Argument.(*gherkin.DocString).Content
 | 
						|
	}
 | 
						|
 | 
						|
	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])
 | 
						|
}
 |