230 строки
5,5 КиБ
Go
230 строки
5,5 КиБ
Go
package godog
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/godog/gherkin"
|
|
)
|
|
|
|
func init() {
|
|
Format("junit", "Prints junit compatible xml to stdout", junitFunc)
|
|
}
|
|
|
|
func junitFunc(suite string, out io.Writer) Formatter {
|
|
return &junitFormatter{
|
|
basefmt: basefmt{
|
|
suiteName: suite,
|
|
started: timeNowFunc(),
|
|
indent: 2,
|
|
out: out,
|
|
},
|
|
lock: new(sync.Mutex),
|
|
}
|
|
}
|
|
|
|
type junitFormatter struct {
|
|
basefmt
|
|
lock *sync.Mutex
|
|
}
|
|
|
|
func (f *junitFormatter) Node(n interface{}) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
f.basefmt.Node(n)
|
|
}
|
|
|
|
func (f *junitFormatter) Feature(ft *gherkin.Feature, p string, c []byte) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
f.basefmt.Feature(ft, p, c)
|
|
}
|
|
|
|
func (f *junitFormatter) Summary() {
|
|
suite := buildJUNITPackageSuite(f.suiteName, f.started, f.features)
|
|
|
|
_, err := io.WriteString(f.out, xml.Header)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "failed to write junit string:", err)
|
|
}
|
|
|
|
enc := xml.NewEncoder(f.out)
|
|
enc.Indent("", s(2))
|
|
if err = enc.Encode(suite); err != nil {
|
|
fmt.Fprintln(os.Stderr, "failed to write junit xml:", err)
|
|
}
|
|
}
|
|
|
|
func (f *junitFormatter) Passed(step *gherkin.Step, match *StepDef) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
f.basefmt.Passed(step, match)
|
|
}
|
|
|
|
func (f *junitFormatter) Skipped(step *gherkin.Step, match *StepDef) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
f.basefmt.Skipped(step, match)
|
|
}
|
|
|
|
func (f *junitFormatter) Undefined(step *gherkin.Step, match *StepDef) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
f.basefmt.Undefined(step, match)
|
|
}
|
|
|
|
func (f *junitFormatter) Failed(step *gherkin.Step, match *StepDef, err error) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
f.basefmt.Failed(step, match, err)
|
|
}
|
|
|
|
func (f *junitFormatter) Pending(step *gherkin.Step, match *StepDef) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
f.basefmt.Pending(step, match)
|
|
}
|
|
|
|
func (f *junitFormatter) Sync(cf ConcurrentFormatter) {
|
|
if source, ok := cf.(*junitFormatter); ok {
|
|
f.lock = source.lock
|
|
}
|
|
}
|
|
|
|
func (f *junitFormatter) Copy(cf ConcurrentFormatter) {
|
|
if source, ok := cf.(*junitFormatter); ok {
|
|
for _, v := range source.features {
|
|
f.features = append(f.features, v)
|
|
}
|
|
for _, v := range source.failed {
|
|
f.failed = append(f.failed, v)
|
|
}
|
|
for _, v := range source.passed {
|
|
f.passed = append(f.passed, v)
|
|
}
|
|
for _, v := range source.skipped {
|
|
f.skipped = append(f.skipped, v)
|
|
}
|
|
for _, v := range source.undefined {
|
|
f.undefined = append(f.undefined, v)
|
|
}
|
|
for _, v := range source.pending {
|
|
f.pending = append(f.pending, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*feature) junitPackageSuite {
|
|
suite := junitPackageSuite{
|
|
Name: suiteName,
|
|
TestSuites: make([]*junitTestSuite, len(features)),
|
|
Time: startedAt.Sub(timeNowFunc()).String(),
|
|
}
|
|
|
|
for idx, feat := range features {
|
|
ts := junitTestSuite{
|
|
Name: feat.Name,
|
|
Time: feat.startedAt().Sub(feat.finishedAt()).String(),
|
|
TestCases: make([]*junitTestCase, len(feat.Scenarios)),
|
|
}
|
|
|
|
for idx, scenario := range feat.Scenarios {
|
|
tc := junitTestCase{}
|
|
tc.Name = scenario.Name
|
|
tc.Time = scenario.startedAt().Sub(scenario.finishedAt()).String()
|
|
|
|
ts.Tests++
|
|
suite.Tests++
|
|
|
|
for _, step := range scenario.Steps {
|
|
switch step.typ {
|
|
case passed:
|
|
tc.Status = passed.String()
|
|
case failed:
|
|
tc.Status = failed.String()
|
|
tc.Failure = &junitFailure{
|
|
Message: fmt.Sprintf("%s %s: %s", step.step.Type, step.step.Text, step.err),
|
|
}
|
|
case skipped:
|
|
tc.Error = append(tc.Error, &junitError{
|
|
Type: "skipped",
|
|
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
|
|
})
|
|
case undefined:
|
|
tc.Status = undefined.String()
|
|
tc.Error = append(tc.Error, &junitError{
|
|
Type: "undefined",
|
|
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
|
|
})
|
|
case pending:
|
|
tc.Status = pending.String()
|
|
tc.Error = append(tc.Error, &junitError{
|
|
Type: "pending",
|
|
Message: fmt.Sprintf("%s %s: TODO: write pending definition", step.step.Type, step.step.Text),
|
|
})
|
|
}
|
|
}
|
|
|
|
switch tc.Status {
|
|
case failed.String():
|
|
ts.Failures++
|
|
suite.Failures++
|
|
case undefined.String(), pending.String():
|
|
ts.Errors++
|
|
suite.Errors++
|
|
}
|
|
|
|
ts.TestCases[idx] = &tc
|
|
}
|
|
|
|
suite.TestSuites[idx] = &ts
|
|
}
|
|
|
|
return suite
|
|
}
|
|
|
|
type junitFailure struct {
|
|
Message string `xml:"message,attr"`
|
|
Type string `xml:"type,attr,omitempty"`
|
|
}
|
|
|
|
type junitError struct {
|
|
XMLName xml.Name `xml:"error,omitempty"`
|
|
Message string `xml:"message,attr"`
|
|
Type string `xml:"type,attr"`
|
|
}
|
|
|
|
type junitTestCase struct {
|
|
XMLName xml.Name `xml:"testcase"`
|
|
Name string `xml:"name,attr"`
|
|
Status string `xml:"status,attr"`
|
|
Time string `xml:"time,attr"`
|
|
Failure *junitFailure `xml:"failure,omitempty"`
|
|
Error []*junitError
|
|
}
|
|
|
|
type junitTestSuite struct {
|
|
XMLName xml.Name `xml:"testsuite"`
|
|
Name string `xml:"name,attr"`
|
|
Tests int `xml:"tests,attr"`
|
|
Skipped int `xml:"skipped,attr"`
|
|
Failures int `xml:"failures,attr"`
|
|
Errors int `xml:"errors,attr"`
|
|
Time string `xml:"time,attr"`
|
|
TestCases []*junitTestCase
|
|
}
|
|
|
|
type junitPackageSuite struct {
|
|
XMLName xml.Name `xml:"testsuites"`
|
|
Name string `xml:"name,attr"`
|
|
Tests int `xml:"tests,attr"`
|
|
Skipped int `xml:"skipped,attr"`
|
|
Failures int `xml:"failures,attr"`
|
|
Errors int `xml:"errors,attr"`
|
|
Time string `xml:"time,attr"`
|
|
TestSuites []*junitTestSuite
|
|
}
|