implementation of event stream formatter
Этот коммит содержится в:
родитель
49e59d9296
коммит
34acb056ea
7 изменённых файлов: 302 добавлений и 14 удалений
26
fmt.go
26
fmt.go
|
@ -84,8 +84,9 @@ func Format(name, description string, f Formatter) {
|
||||||
// formatters needs to be registered with a
|
// formatters needs to be registered with a
|
||||||
// godog.Format function call
|
// godog.Format function call
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Feature(*gherkin.Feature, string)
|
Feature(*gherkin.Feature, string, []byte)
|
||||||
Node(interface{})
|
Node(interface{})
|
||||||
|
Defined(*gherkin.Step, *StepDef)
|
||||||
Failed(*gherkin.Step, *StepDef, error)
|
Failed(*gherkin.Step, *StepDef, error)
|
||||||
Passed(*gherkin.Step, *StepDef)
|
Passed(*gherkin.Step, *StepDef)
|
||||||
Skipped(*gherkin.Step)
|
Skipped(*gherkin.Step)
|
||||||
|
@ -117,6 +118,23 @@ func (st stepType) clr() color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st stepType) String() string {
|
||||||
|
switch st {
|
||||||
|
case passed:
|
||||||
|
return "passed"
|
||||||
|
case failed:
|
||||||
|
return "failed"
|
||||||
|
case skipped:
|
||||||
|
return "skipped"
|
||||||
|
case undefined:
|
||||||
|
return "undefined"
|
||||||
|
case pending:
|
||||||
|
return "pending"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type stepResult struct {
|
type stepResult struct {
|
||||||
typ stepType
|
typ stepType
|
||||||
feature *feature
|
feature *feature
|
||||||
|
@ -152,7 +170,11 @@ func (f *basefmt) Node(n interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Feature(ft *gherkin.Feature, p string) {
|
func (f *basefmt) Defined(*gherkin.Step, *StepDef) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *basefmt) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||||
f.features = append(f.features, &feature{Path: p, Feature: ft})
|
f.features = append(f.features, &feature{Path: p, Feature: ft})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
256
fmt_events.go
Обычный файл
256
fmt_events.go
Обычный файл
|
@ -0,0 +1,256 @@
|
||||||
|
package godog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/cucumber/gherkin-go.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const nanoSec = 1000000
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Format("events", "Produces a JSON event stream.", &events{
|
||||||
|
basefmt: basefmt{
|
||||||
|
started: time.Now(),
|
||||||
|
indent: 2,
|
||||||
|
},
|
||||||
|
runID: sha1RunID(),
|
||||||
|
suite: "main",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func sha1RunID() string {
|
||||||
|
data, err := json.Marshal(&struct {
|
||||||
|
Time int64
|
||||||
|
Runner string
|
||||||
|
}{time.Now().UnixNano(), "godog"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to marshal run id")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := sha1.New()
|
||||||
|
hasher.Write(data)
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
type events struct {
|
||||||
|
sync.Once
|
||||||
|
basefmt
|
||||||
|
|
||||||
|
runID string
|
||||||
|
suite string
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) event(ev interface{}) {
|
||||||
|
data, err := json.Marshal(ev)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to marshal stream event: %+v - %v", ev, err))
|
||||||
|
}
|
||||||
|
fmt.Println(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) started() {
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}{
|
||||||
|
"TestRunStarted",
|
||||||
|
f.runID,
|
||||||
|
"0.1.0",
|
||||||
|
time.Now().UnixNano() / nanoSec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Node(n interface{}) {
|
||||||
|
f.basefmt.Node(n)
|
||||||
|
f.Do(f.started)
|
||||||
|
|
||||||
|
switch t := n.(type) {
|
||||||
|
case *gherkin.Scenario:
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Suite string `json:"suite"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}{
|
||||||
|
"TestCaseStarted",
|
||||||
|
f.runID,
|
||||||
|
"main",
|
||||||
|
fmt.Sprintf("%s:%d", f.path, t.Location.Line),
|
||||||
|
time.Now().UnixNano() / nanoSec,
|
||||||
|
})
|
||||||
|
case *gherkin.TableRow:
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Suite string `json:"suite"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}{
|
||||||
|
"TestCaseStarted",
|
||||||
|
f.runID,
|
||||||
|
"main",
|
||||||
|
fmt.Sprintf("%s:%d", f.path, t.Location.Line),
|
||||||
|
time.Now().UnixNano() / nanoSec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||||
|
f.basefmt.Feature(ft, p, c)
|
||||||
|
f.path = p
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
}{
|
||||||
|
"TestSource",
|
||||||
|
f.runID,
|
||||||
|
fmt.Sprintf("%s:%d", p, ft.Location.Line),
|
||||||
|
string(c),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Summary() {
|
||||||
|
// determine status
|
||||||
|
status := passed
|
||||||
|
if len(f.failed) > 0 {
|
||||||
|
status = failed
|
||||||
|
} else if len(f.passed) == 0 {
|
||||||
|
if len(f.undefined) > len(f.pending) {
|
||||||
|
status = undefined
|
||||||
|
} else {
|
||||||
|
status = pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}{
|
||||||
|
"TestRunFinished",
|
||||||
|
f.runID,
|
||||||
|
status.String(),
|
||||||
|
time.Now().UnixNano() / nanoSec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) step(res *stepResult) {
|
||||||
|
var errMsg string
|
||||||
|
if res.err != nil {
|
||||||
|
errMsg = res.err.Error()
|
||||||
|
}
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Suite string `json:"suite"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Summary string `json:"summary,omitempty"`
|
||||||
|
}{
|
||||||
|
"TestStepFinished",
|
||||||
|
f.runID,
|
||||||
|
"main",
|
||||||
|
fmt.Sprintf("%s:%d", f.path, res.step.Location.Line),
|
||||||
|
time.Now().UnixNano() / nanoSec,
|
||||||
|
res.typ.String(),
|
||||||
|
errMsg,
|
||||||
|
})
|
||||||
|
|
||||||
|
switch t := f.owner.(type) {
|
||||||
|
case *gherkin.ScenarioOutline:
|
||||||
|
case *gherkin.Scenario:
|
||||||
|
for i, s := range t.Steps {
|
||||||
|
if s.Location.Line == res.step.Location.Line && i == len(t.Steps)-1 {
|
||||||
|
// what if scenario is empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Defined(step *gherkin.Step, def *StepDef) {
|
||||||
|
if def != nil {
|
||||||
|
m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]
|
||||||
|
args := make([][2]int, 0)
|
||||||
|
for i := 0; i < len(m)/2; i++ {
|
||||||
|
pair := m[i : i*2+2]
|
||||||
|
var idxs [2]int
|
||||||
|
idxs[0] = pair[0]
|
||||||
|
idxs[1] = pair[1]
|
||||||
|
args = append(args, idxs)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Suite string `json:"suite"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
DefID string `json:"definition_id"`
|
||||||
|
Args [][2]int `json:"arguments"`
|
||||||
|
}{
|
||||||
|
"StepDefinitionFound",
|
||||||
|
f.runID,
|
||||||
|
"main",
|
||||||
|
fmt.Sprintf("%s:%d", f.path, step.Location.Line),
|
||||||
|
def.definitionID(),
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
f.event(&struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
Suite string `json:"suite"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}{
|
||||||
|
"TestStepStarted",
|
||||||
|
f.runID,
|
||||||
|
"main",
|
||||||
|
fmt.Sprintf("%s:%d", f.path, step.Location.Line),
|
||||||
|
time.Now().UnixNano() / nanoSec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Passed(step *gherkin.Step, match *StepDef) {
|
||||||
|
f.basefmt.Passed(step, match)
|
||||||
|
f.step(f.passed[len(f.passed)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Skipped(step *gherkin.Step) {
|
||||||
|
f.basefmt.Skipped(step)
|
||||||
|
f.step(f.skipped[len(f.skipped)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Undefined(step *gherkin.Step) {
|
||||||
|
f.basefmt.Undefined(step)
|
||||||
|
f.step(f.undefined[len(f.undefined)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||||
|
f.basefmt.Failed(step, match, err)
|
||||||
|
f.step(f.failed[len(f.failed)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *events) Pending(step *gherkin.Step, match *StepDef) {
|
||||||
|
f.basefmt.Pending(step, match)
|
||||||
|
f.step(f.pending[len(f.pending)-1])
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ type junitFormatter struct {
|
||||||
outlineExample int
|
outlineExample int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *junitFormatter) Feature(feature *gherkin.Feature, path string) {
|
func (j *junitFormatter) Feature(feature *gherkin.Feature, path string, c []byte) {
|
||||||
testSuite := &junitTestSuite{
|
testSuite := &junitTestSuite{
|
||||||
TestCases: make([]*junitTestCase, 0),
|
TestCases: make([]*junitTestCase, 0),
|
||||||
Name: feature.Name,
|
Name: feature.Name,
|
||||||
|
@ -45,6 +45,10 @@ func (j *junitFormatter) Feature(feature *gherkin.Feature, path string) {
|
||||||
j.suite.TestSuites = append(j.suite.TestSuites, testSuite)
|
j.suite.TestSuites = append(j.suite.TestSuites, testSuite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *junitFormatter) Defined(*gherkin.Step, *StepDef) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (j *junitFormatter) Node(node interface{}) {
|
func (j *junitFormatter) Node(node interface{}) {
|
||||||
suite := j.current()
|
suite := j.current()
|
||||||
tcase := &junitTestCase{}
|
tcase := &junitTestCase{}
|
||||||
|
|
|
@ -45,7 +45,7 @@ type pretty struct {
|
||||||
outlineNumExamples int
|
outlineNumExamples int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) Feature(ft *gherkin.Feature, p string) {
|
func (f *pretty) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||||
if len(f.features) != 0 {
|
if len(f.features) != 0 {
|
||||||
// not a first feature, add a newline
|
// not a first feature, add a newline
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
@ -168,7 +168,7 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
|
||||||
} else {
|
} else {
|
||||||
text = cl(ostep.Text, cyan)
|
text = cl(ostep.Text, cyan)
|
||||||
}
|
}
|
||||||
text += s(f.commentPos-f.length(ostep)+1) + cl(fmt.Sprintf("# %s", res.def.funcName()), black)
|
text += s(f.commentPos-f.length(ostep)+1) + cl(fmt.Sprintf("# %s", res.def.definitionID()), black)
|
||||||
} else {
|
} else {
|
||||||
text = cl(ostep.Text, cyan)
|
text = cl(ostep.Text, cyan)
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
|
||||||
text := s(f.indent*2) + cl(strings.TrimSpace(step.Keyword), c) + " "
|
text := s(f.indent*2) + cl(strings.TrimSpace(step.Keyword), c) + " "
|
||||||
switch {
|
switch {
|
||||||
case def != nil:
|
case def != nil:
|
||||||
if m := (def.Expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 {
|
if m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]; len(m) > 0 {
|
||||||
var pos, i int
|
var pos, i int
|
||||||
for pos, i = 0, 0; i < len(m); i++ {
|
for pos, i = 0, 0; i < len(m); i++ {
|
||||||
if math.Mod(float64(i), 2) == 0 {
|
if math.Mod(float64(i), 2) == 0 {
|
||||||
|
@ -221,7 +221,7 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
|
||||||
} else {
|
} else {
|
||||||
text += cl(step.Text, c)
|
text += cl(step.Text, c)
|
||||||
}
|
}
|
||||||
text += s(f.commentPos-f.length(step)+1) + cl(fmt.Sprintf("# %s", def.funcName()), black)
|
text += s(f.commentPos-f.length(step)+1) + cl(fmt.Sprintf("# %s", def.definitionID()), black)
|
||||||
default:
|
default:
|
||||||
text += cl(step.Text, c)
|
text += cl(step.Text, c)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,10 @@ func (f *progress) Node(n interface{}) {
|
||||||
f.basefmt.Node(n)
|
f.basefmt.Node(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Feature(ft *gherkin.Feature, p string) {
|
func (f *progress) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||||
f.Lock()
|
f.Lock()
|
||||||
defer f.Unlock()
|
defer f.Unlock()
|
||||||
f.basefmt.Feature(ft, p)
|
f.basefmt.Feature(ft, p, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Summary() {
|
func (f *progress) Summary() {
|
||||||
|
|
|
@ -29,7 +29,7 @@ type StepDef struct {
|
||||||
Handler interface{}
|
Handler interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *StepDef) funcName() string {
|
func (sd *StepDef) definitionID() string {
|
||||||
ptr := sd.hv.Pointer()
|
ptr := sd.hv.Pointer()
|
||||||
f := runtime.FuncForPC(ptr)
|
f := runtime.FuncForPC(ptr)
|
||||||
file, line := f.FileLine(ptr)
|
file, line := f.FileLine(ptr)
|
||||||
|
|
14
suite.go
14
suite.go
|
@ -1,7 +1,9 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -19,7 +21,8 @@ var typeOfBytes = reflect.TypeOf([]byte(nil))
|
||||||
|
|
||||||
type feature struct {
|
type feature struct {
|
||||||
*gherkin.Feature
|
*gherkin.Feature
|
||||||
Path string `json:"path"`
|
Content []byte `json:"-"`
|
||||||
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUndefined is returned in case if step definition was not found
|
// ErrUndefined is returned in case if step definition was not found
|
||||||
|
@ -193,11 +196,13 @@ func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// @TODO can handle ambiguous
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||||
match := s.matchStep(step)
|
match := s.matchStep(step)
|
||||||
|
s.fmt.Defined(step, match)
|
||||||
if match == nil {
|
if match == nil {
|
||||||
s.fmt.Undefined(step)
|
s.fmt.Undefined(step)
|
||||||
return ErrUndefined
|
return ErrUndefined
|
||||||
|
@ -325,7 +330,7 @@ func (s *Suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Backgrou
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runFeature(f *feature) {
|
func (s *Suite) runFeature(f *feature) {
|
||||||
s.fmt.Feature(f.Feature, f.Path)
|
s.fmt.Feature(f.Feature, f.Path, f.Content)
|
||||||
for _, scenario := range f.ScenarioDefinitions {
|
for _, scenario := range f.ScenarioDefinitions {
|
||||||
var err error
|
var err error
|
||||||
if f.Background != nil {
|
if f.Background != nil {
|
||||||
|
@ -408,12 +413,13 @@ func parseFeatures(filter string, paths []string) (features []*feature, err erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ft, err := gherkin.ParseFeature(reader)
|
var buf bytes.Buffer
|
||||||
|
ft, err := gherkin.ParseFeature(io.TeeReader(reader, &buf))
|
||||||
reader.Close()
|
reader.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
features = append(features, &feature{Path: p, Feature: ft})
|
features = append(features, &feature{Path: p, Feature: ft, Content: buf.Bytes()})
|
||||||
// filter scenario by line number
|
// filter scenario by line number
|
||||||
if line != -1 {
|
if line != -1 {
|
||||||
var scenarios []interface{}
|
var scenarios []interface{}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче