simplify cucumber output format matcher for now
Этот коммит содержится в:
родитель
f726a8ed55
коммит
96efe41c4d
3 изменённых файлов: 23 добавлений и 150 удалений
|
@ -257,7 +257,7 @@ Feature: cucumber json formatter
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 7,
|
"line": 7,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "STEP_ID"
|
"location": "suite_test.go:69"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
|
@ -269,7 +269,7 @@ Feature: cucumber json formatter
|
||||||
"name": "a failing step",
|
"name": "a failing step",
|
||||||
"line": 8,
|
"line": 8,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "STEP_ID"
|
"location": "suite_test.go:52"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
|
@ -325,7 +325,7 @@ Feature: cucumber json formatter
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 11,
|
"line": 11,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "STEP_ID"
|
"location": "suite_test.go:69"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
|
@ -347,7 +347,7 @@ Feature: cucumber json formatter
|
||||||
"name": "failing step",
|
"name": "failing step",
|
||||||
"line": 12,
|
"line": 12,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "STEP_ID"
|
"location": "suite_test.go:52"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
|
@ -448,7 +448,7 @@ Feature: cucumber json formatter
|
||||||
"line": 8
|
"line": 8
|
||||||
},
|
},
|
||||||
"match": {
|
"match": {
|
||||||
"location": "STEP_ID"
|
"location": "suite_test.go:69"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
|
@ -501,11 +501,11 @@ Feature: cucumber json formatter
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 7,
|
"line": 7,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "STEP_ID"
|
"location": "suite_test.go:69"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
"duration": 397087
|
"duration": -1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -513,7 +513,7 @@ Feature: cucumber json formatter
|
||||||
"name": "pending step",
|
"name": "pending step",
|
||||||
"line": 8,
|
"line": 8,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "FEATURE_PATH features/simple.feature:8"
|
"location": "features/simple.feature:8"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "pending"
|
"status": "pending"
|
||||||
|
@ -524,7 +524,7 @@ Feature: cucumber json formatter
|
||||||
"name": "undefined",
|
"name": "undefined",
|
||||||
"line": 9,
|
"line": 9,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "FEATURE_PATH features/simple.feature:9"
|
"location": "features/simple.feature:9"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "undefined"
|
"status": "undefined"
|
||||||
|
@ -535,7 +535,7 @@ Feature: cucumber json formatter
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 10,
|
"line": 10,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "STEP_ID"
|
"location": "suite_test.go:69"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "skipped"
|
"status": "skipped"
|
||||||
|
|
|
@ -14,17 +14,16 @@ package godog
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/DATA-DOG/godog/gherkin"
|
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/godog/gherkin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cukeurl = "https://www.relishapp.com/cucumber/cucumber/docs/formatters/json-output-formatter"
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Format("cucumber", fmt.Sprintf("Produces cucumber JSON stream, based on spec @: %s.", cukeurl), cucumberFunc)
|
Format("cucumber", "Produces cucumber JSON format output.", cucumberFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cucumberFunc(suite string, out io.Writer) Formatter {
|
func cucumberFunc(suite string, out io.Writer) Formatter {
|
||||||
|
|
146
suite_test.go
146
suite_test.go
|
@ -4,13 +4,15 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/DATA-DOG/godog/gherkin"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/godog/gherkin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -388,148 +390,20 @@ func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.Data
|
||||||
|
|
||||||
func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
|
func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
|
||||||
|
|
||||||
var expected interface{}
|
var expected []cukeFeatureJSON
|
||||||
if err := json.Unmarshal([]byte(docstring.Content), &expected); err != nil {
|
if err := json.Unmarshal([]byte(docstring.Content), &expected); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var actual interface{}
|
var actual []cukeFeatureJSON
|
||||||
if err := json.Unmarshal(s.out.Bytes(), &actual); err != nil {
|
re := regexp.MustCompile(`"duration":\s*\d+`)
|
||||||
|
replaced := re.ReplaceAllString(s.out.String(), `"duration": -1`)
|
||||||
|
if err := json.Unmarshal([]byte(replaced), &actual); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedArr := expected.([]interface{})
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
actualArr := actual.([]interface{})
|
return fmt.Errorf("expected json does not match actual: %s", replaced)
|
||||||
|
|
||||||
// Created to use in error reporting.
|
|
||||||
expectedCompact := &bytes.Buffer{}
|
|
||||||
actualCompact := &bytes.Buffer{}
|
|
||||||
json.Compact(expectedCompact, []byte(docstring.Content))
|
|
||||||
json.Compact(actualCompact, s.out.Bytes())
|
|
||||||
|
|
||||||
for idx, entry := range expectedArr {
|
|
||||||
|
|
||||||
// Make sure all of the expected are in the actual
|
|
||||||
if err := s.mapCompareStructure(entry.(map[string]interface{}), actualArr[idx].(map[string]interface{})); err != nil {
|
|
||||||
return fmt.Errorf("err:%v actual result is missing fields: expected:%s actual:%s", err, expectedCompact, actualCompact)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure all of actual are in expected
|
|
||||||
if err := s.mapCompareStructure(actualArr[idx].(map[string]interface{}), entry.(map[string]interface{})); err != nil {
|
|
||||||
return fmt.Errorf("err:%v actual result contains too many fields: expected:%s actual:%s", err, expectedCompact, actualCompact)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the values are correct
|
|
||||||
if err := s.mapCompare(entry.(map[string]interface{}), actualArr[idx].(map[string]interface{})); err != nil {
|
|
||||||
return fmt.Errorf("err:%v values don't match expected:%s actual:%s", err, expectedCompact, actualCompact)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
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.
|
|
||||||
*/
|
|
||||||
func (s *suiteContext) mapCompare(expected map[string]interface{}, actual map[string]interface{}) error {
|
|
||||||
|
|
||||||
// Process all keys in the map and handle them based on the type of the field.
|
|
||||||
for k, v := range expected {
|
|
||||||
|
|
||||||
if actual[k] == nil {
|
|
||||||
return fmt.Errorf("No matching field in actual:[%s] expected value:[%v]", k, v)
|
|
||||||
}
|
|
||||||
// Process other maps via recursion
|
|
||||||
if reflect.TypeOf(v).Kind() == reflect.Map {
|
|
||||||
if err := s.mapCompare(v.(map[string]interface{}), actual[k].(map[string]interface{})); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// This is an array of maps show as a slice
|
|
||||||
} else if reflect.TypeOf(v).Kind() == reflect.Slice {
|
|
||||||
for i, e := range v.([]interface{}) {
|
|
||||||
if err := s.mapCompare(e.(map[string]interface{}), actual[k].([]interface{})[i].(map[string]interface{})); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We need special rules to check location so that we are not bound to version of the code.
|
|
||||||
} else if k == "location" {
|
|
||||||
|
|
||||||
// location is tricky. the cucumber value is either a the step def location for passed,failed, and skipped.
|
|
||||||
// it is the feature file location for undefined and skipped.
|
|
||||||
// I dont have the result context readily available so the expected input will have
|
|
||||||
// the context i need contained within its value.
|
|
||||||
// FEATURE_PATH myfile.feature:20 or
|
|
||||||
// STEP_ID
|
|
||||||
t := strings.Split(v.(string), " ")
|
|
||||||
if t[0] == "FEATURE_PATH" {
|
|
||||||
if actual[k].(string) != t[1] {
|
|
||||||
return fmt.Errorf("location has unexpected value [%s] should be [%s]",
|
|
||||||
actual[k], t[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if t[0] == "STEP_ID" {
|
|
||||||
if !strings.Contains(actual[k].(string), "suite_test.go:") {
|
|
||||||
return fmt.Errorf("location has unexpected filename [%s] should contain suite_test.go",
|
|
||||||
actual[k])
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Bad location value [%v]", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need special rules to validate duration too.
|
|
||||||
} else if k == "duration" {
|
|
||||||
if actual[k].(float64) <= 0 {
|
|
||||||
return fmt.Errorf("duration is <= zero: actual:[%v]", actual[k])
|
|
||||||
}
|
|
||||||
// default numbers in json are coming as float64
|
|
||||||
} else if reflect.TypeOf(v).Kind() == reflect.Float64 {
|
|
||||||
if v.(float64) != actual[k].(float64) {
|
|
||||||
if v.(float64) != actual[k].(float64) {
|
|
||||||
return fmt.Errorf("Field:[%s] not matching expected:[%v] actual:[%v]",
|
|
||||||
k, v, actual[k])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if reflect.TypeOf(v).Kind() == reflect.String {
|
|
||||||
if v.(string) != actual[k].(string) {
|
|
||||||
return fmt.Errorf("Field:[%s] not matching expected:[%v] actual:[%v]",
|
|
||||||
k, v, actual[k])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Unexepcted type encountered in json at key:[%s] Type:[%v]", k, reflect.TypeOf(v).Kind())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
func (s *suiteContext) mapCompareStructure(expected map[string]interface{}, actual map[string]interface{}) error {
|
|
||||||
|
|
||||||
// Process all keys in the map and handle them based on the type of the field.
|
|
||||||
for k, v := range expected {
|
|
||||||
|
|
||||||
if actual[k] == nil {
|
|
||||||
return fmt.Errorf("Structure Mismatch: no matching field:[%s] expected value:[%v]", k, v)
|
|
||||||
}
|
|
||||||
// Process other maps via recursion
|
|
||||||
if reflect.TypeOf(v).Kind() == reflect.Map {
|
|
||||||
if err := s.mapCompareStructure(v.(map[string]interface{}), actual[k].(map[string]interface{})); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// This is an array of maps show as a slice
|
|
||||||
} else if reflect.TypeOf(v).Kind() == reflect.Slice {
|
|
||||||
for i, e := range v.([]interface{}) {
|
|
||||||
if err := s.mapCompareStructure(e.(map[string]interface{}), actual[k].([]interface{})[i].(map[string]interface{})); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче