
update messages-go to v16.0.1 bump gomod version comment on log line in std os.Stderr examples to non rc version go mod tidy update circle (tbd)
305 строки
8,5 КиБ
Go
305 строки
8,5 КиБ
Go
package formatters
|
|
|
|
/*
|
|
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 (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/cucumber/messages-go/v16"
|
|
|
|
"github.com/cucumber/godog/formatters"
|
|
"github.com/cucumber/godog/internal/models"
|
|
)
|
|
|
|
func init() {
|
|
formatters.Format("cucumber", "Produces cucumber JSON format output.", CucumberFormatterFunc)
|
|
}
|
|
|
|
// CucumberFormatterFunc implements the FormatterFunc for the cucumber formatter
|
|
func CucumberFormatterFunc(suite string, out io.Writer) formatters.Formatter {
|
|
return &cukefmt{Basefmt: NewBaseFmt(suite, out)}
|
|
}
|
|
|
|
type cukefmt struct {
|
|
*Basefmt
|
|
}
|
|
|
|
func (f *cukefmt) Summary() {
|
|
features := f.storage.MustGetFeatures()
|
|
|
|
res := f.buildCukeFeatures(features)
|
|
|
|
dat, err := json.MarshalIndent(res, "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Fprintf(f.out, "%s\n", string(dat))
|
|
}
|
|
|
|
func (f *cukefmt) buildCukeFeatures(features []*models.Feature) (res []CukeFeatureJSON) {
|
|
sort.Sort(sortFeaturesByName(features))
|
|
|
|
res = make([]CukeFeatureJSON, len(features))
|
|
|
|
for idx, feat := range features {
|
|
cukeFeature := buildCukeFeature(feat)
|
|
|
|
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
|
|
elem.Tags = append(cukeFeature.Tags, elem.Tags...)
|
|
cukeFeature.Elements[jdx] = elem
|
|
}
|
|
|
|
res[idx] = cukeFeature
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func (f *cukefmt) buildCukeElements(pickles []*messages.Pickle) (res []cukeElement) {
|
|
res = make([]cukeElement, len(pickles))
|
|
|
|
for idx, pickle := range pickles {
|
|
pickleResult := f.storage.MustGetPickleResult(pickle.Id)
|
|
pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id)
|
|
|
|
cukeElement := f.buildCukeElement(pickle)
|
|
|
|
stepStartedAt := pickleResult.StartedAt
|
|
|
|
cukeElement.Steps = make([]cukeStep, len(pickleStepResults))
|
|
sort.Sort(sortPickleStepResultsByPickleStepID(pickleStepResults))
|
|
|
|
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 {
|
|
cukeStep.Result.Duration = nil
|
|
}
|
|
|
|
cukeElement.Steps[jdx] = cukeStep
|
|
}
|
|
|
|
res[idx] = cukeElement
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
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,omitempty"`
|
|
}
|
|
|
|
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"`
|
|
DataTable []*cukeDataTableRow `json:"rows,omitempty"`
|
|
}
|
|
|
|
type cukeDataTableRow struct {
|
|
Cells []string `json:"cells"`
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
// CukeFeatureJSON ...
|
|
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"`
|
|
}
|
|
|
|
func buildCukeFeature(feat *models.Feature) CukeFeatureJSON {
|
|
cukeFeature := CukeFeatureJSON{
|
|
URI: feat.Uri,
|
|
ID: makeCukeID(feat.Feature.Name),
|
|
Keyword: feat.Feature.Keyword,
|
|
Name: feat.Feature.Name,
|
|
Description: feat.Feature.Description,
|
|
Line: int(feat.Feature.Location.Line),
|
|
Comments: make([]cukeComment, len(feat.Comments)),
|
|
Tags: make([]cukeTag, len(feat.Feature.Tags)),
|
|
}
|
|
|
|
for idx, element := range feat.Feature.Tags {
|
|
cukeFeature.Tags[idx].Line = int(element.Location.Line)
|
|
cukeFeature.Tags[idx].Name = element.Name
|
|
}
|
|
|
|
for idx, comment := range feat.Comments {
|
|
cukeFeature.Comments[idx].Value = strings.TrimSpace(comment.Text)
|
|
cukeFeature.Comments[idx].Line = int(comment.Location.Line)
|
|
}
|
|
|
|
return cukeFeature
|
|
}
|
|
|
|
func (f *cukefmt) buildCukeElement(pickle *messages.Pickle) (cukeElement cukeElement) {
|
|
feature := f.storage.MustGetFeature(pickle.Uri)
|
|
scenario := feature.FindScenario(pickle.AstNodeIds[0])
|
|
|
|
cukeElement.Name = pickle.Name
|
|
cukeElement.Line = int(scenario.Location.Line)
|
|
cukeElement.Description = scenario.Description
|
|
cukeElement.Keyword = scenario.Keyword
|
|
cukeElement.Type = "scenario"
|
|
|
|
cukeElement.Tags = make([]cukeTag, len(scenario.Tags))
|
|
for idx, element := range scenario.Tags {
|
|
cukeElement.Tags[idx].Line = int(element.Location.Line)
|
|
cukeElement.Tags[idx].Name = element.Name
|
|
}
|
|
|
|
if len(pickle.AstNodeIds) == 1 {
|
|
return
|
|
}
|
|
|
|
example, _ := feature.FindExample(pickle.AstNodeIds[1])
|
|
|
|
for _, tag := range example.Tags {
|
|
tag := cukeTag{Line: int(tag.Location.Line), Name: tag.Name}
|
|
cukeElement.Tags = append(cukeElement.Tags, tag)
|
|
}
|
|
|
|
examples := scenario.Examples
|
|
if len(examples) > 0 {
|
|
rowID := pickle.AstNodeIds[1]
|
|
|
|
for _, example := range examples {
|
|
for idx, row := range example.TableBody {
|
|
if rowID == row.Id {
|
|
cukeElement.ID += fmt.Sprintf(";%s;%d", makeCukeID(example.Name), idx+2)
|
|
cukeElement.Line = int(row.Location.Line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return cukeElement
|
|
}
|
|
|
|
func (f *cukefmt) buildCukeStep(pickle *messages.Pickle, stepResult models.PickleStepResult) (cukeStep cukeStep) {
|
|
feature := f.storage.MustGetFeature(pickle.Uri)
|
|
pickleStep := f.storage.MustGetPickleStep(stepResult.PickleStepID)
|
|
step := feature.FindStep(pickleStep.AstNodeIds[0])
|
|
|
|
line := step.Location.Line
|
|
if len(pickle.AstNodeIds) == 2 {
|
|
_, row := feature.FindExample(pickle.AstNodeIds[1])
|
|
line = row.Location.Line
|
|
}
|
|
|
|
cukeStep.Name = pickleStep.Text
|
|
cukeStep.Line = int(line)
|
|
cukeStep.Keyword = step.Keyword
|
|
|
|
arg := pickleStep.Argument
|
|
|
|
if arg != nil {
|
|
if arg.DocString != nil && step.DocString != nil {
|
|
cukeStep.Docstring = &cukeDocstring{}
|
|
cukeStep.Docstring.ContentType = strings.TrimSpace(arg.DocString.MediaType)
|
|
if step.Location != nil {
|
|
cukeStep.Docstring.Line = int(step.DocString.Location.Line)
|
|
}
|
|
cukeStep.Docstring.Value = arg.DocString.Content
|
|
}
|
|
|
|
if arg.DataTable != nil {
|
|
cukeStep.DataTable = make([]*cukeDataTableRow, len(arg.DataTable.Rows))
|
|
for i, row := range arg.DataTable.Rows {
|
|
cells := make([]string, len(row.Cells))
|
|
for j, cell := range row.Cells {
|
|
cells[j] = cell.Value
|
|
}
|
|
cukeStep.DataTable[i] = &cukeDataTableRow{Cells: cells}
|
|
}
|
|
}
|
|
}
|
|
|
|
if stepResult.Def != nil {
|
|
cukeStep.Match.Location = strings.Split(DefinitionID(stepResult.Def), " ")[0]
|
|
}
|
|
|
|
cukeStep.Result.Status = stepResult.Status.String()
|
|
if stepResult.Err != nil {
|
|
cukeStep.Result.Error = stepResult.Err.Error()
|
|
}
|
|
|
|
if stepResult.Status == undefined || stepResult.Status == pending {
|
|
cukeStep.Match.Location = fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line)
|
|
}
|
|
|
|
return cukeStep
|
|
}
|
|
|
|
func makeCukeID(name string) string {
|
|
return strings.Replace(strings.ToLower(name), " ", "-", -1)
|
|
}
|