github.com/maps90/godog@v0.7.5-0.20170923143419-0093943021d4/fmt_junit.go (about) 1 package godog 2 3 import ( 4 "encoding/xml" 5 "fmt" 6 "io" 7 "os" 8 "time" 9 10 "github.com/DATA-DOG/godog/gherkin" 11 ) 12 13 func init() { 14 Format("junit", "Prints junit compatible xml to stdout", junitFunc) 15 } 16 17 func junitFunc(suite string, out io.Writer) Formatter { 18 return &junitFormatter{ 19 suite: &junitPackageSuite{ 20 Name: suite, 21 TestSuites: make([]*junitTestSuite, 0), 22 }, 23 out: out, 24 started: timeNowFunc(), 25 } 26 } 27 28 type junitFormatter struct { 29 suite *junitPackageSuite 30 out io.Writer 31 32 // timing 33 started time.Time 34 caseStarted time.Time 35 featStarted time.Time 36 37 outline *gherkin.ScenarioOutline 38 outlineExample int 39 } 40 41 func (j *junitFormatter) Feature(feature *gherkin.Feature, path string, c []byte) { 42 testSuite := &junitTestSuite{ 43 TestCases: make([]*junitTestCase, 0), 44 Name: feature.Name, 45 } 46 47 if len(j.suite.TestSuites) > 0 { 48 j.current().Time = timeNowFunc().Sub(j.featStarted).String() 49 } 50 j.featStarted = timeNowFunc() 51 j.suite.TestSuites = append(j.suite.TestSuites, testSuite) 52 } 53 54 func (j *junitFormatter) Defined(*gherkin.Step, *StepDef) { 55 56 } 57 58 func (j *junitFormatter) Node(node interface{}) { 59 suite := j.current() 60 tcase := &junitTestCase{} 61 62 switch t := node.(type) { 63 case *gherkin.ScenarioOutline: 64 j.outline = t 65 j.outlineExample = 0 66 return 67 case *gherkin.Scenario: 68 tcase.Name = t.Name 69 suite.Tests++ 70 j.suite.Tests++ 71 case *gherkin.TableRow: 72 j.outlineExample++ 73 tcase.Name = fmt.Sprintf("%s #%d", j.outline.Name, j.outlineExample) 74 suite.Tests++ 75 j.suite.Tests++ 76 default: 77 return 78 } 79 j.caseStarted = timeNowFunc() 80 suite.TestCases = append(suite.TestCases, tcase) 81 } 82 83 func (j *junitFormatter) Failed(step *gherkin.Step, match *StepDef, err error) { 84 suite := j.current() 85 suite.Failures++ 86 j.suite.Failures++ 87 88 tcase := suite.current() 89 tcase.Time = timeNowFunc().Sub(j.caseStarted).String() 90 tcase.Status = "failed" 91 tcase.Failure = &junitFailure{ 92 Message: fmt.Sprintf("%s %s: %s", step.Type, step.Text, err.Error()), 93 } 94 } 95 96 func (j *junitFormatter) Passed(step *gherkin.Step, match *StepDef) { 97 suite := j.current() 98 99 tcase := suite.current() 100 tcase.Time = timeNowFunc().Sub(j.caseStarted).String() 101 tcase.Status = "passed" 102 } 103 104 func (j *junitFormatter) Skipped(step *gherkin.Step, match *StepDef) { 105 suite := j.current() 106 107 tcase := suite.current() 108 tcase.Time = timeNowFunc().Sub(j.caseStarted).String() 109 tcase.Error = append(tcase.Error, &junitError{ 110 Type: "skipped", 111 Message: fmt.Sprintf("%s %s", step.Type, step.Text), 112 }) 113 } 114 115 func (j *junitFormatter) Undefined(step *gherkin.Step, match *StepDef) { 116 suite := j.current() 117 tcase := suite.current() 118 if tcase.Status != "undefined" { 119 // do not count two undefined steps as another error 120 suite.Errors++ 121 j.suite.Errors++ 122 } 123 124 tcase.Time = timeNowFunc().Sub(j.caseStarted).String() 125 tcase.Status = "undefined" 126 tcase.Error = append(tcase.Error, &junitError{ 127 Type: "undefined", 128 Message: fmt.Sprintf("%s %s", step.Type, step.Text), 129 }) 130 } 131 132 func (j *junitFormatter) Pending(step *gherkin.Step, match *StepDef) { 133 suite := j.current() 134 suite.Errors++ 135 j.suite.Errors++ 136 137 tcase := suite.current() 138 tcase.Time = timeNowFunc().Sub(j.caseStarted).String() 139 tcase.Status = "pending" 140 tcase.Error = append(tcase.Error, &junitError{ 141 Type: "pending", 142 Message: fmt.Sprintf("%s %s: TODO: write pending definition", step.Type, step.Text), 143 }) 144 } 145 146 func (j *junitFormatter) Summary() { 147 if j.current() != nil { 148 j.current().Time = timeNowFunc().Sub(j.featStarted).String() 149 } 150 j.suite.Time = timeNowFunc().Sub(j.started).String() 151 io.WriteString(j.out, xml.Header) 152 153 enc := xml.NewEncoder(j.out) 154 enc.Indent("", s(2)) 155 if err := enc.Encode(j.suite); err != nil { 156 fmt.Fprintln(os.Stderr, "failed to write junit xml:", err) 157 } 158 } 159 160 type junitFailure struct { 161 Message string `xml:"message,attr"` 162 Type string `xml:"type,attr,omitempty"` 163 } 164 165 type junitError struct { 166 XMLName xml.Name `xml:"error,omitempty"` 167 Message string `xml:"message,attr"` 168 Type string `xml:"type,attr"` 169 } 170 171 type junitTestCase struct { 172 XMLName xml.Name `xml:"testcase"` 173 Name string `xml:"name,attr"` 174 Status string `xml:"status,attr"` 175 Time string `xml:"time,attr"` 176 Failure *junitFailure `xml:"failure,omitempty"` 177 Error []*junitError 178 } 179 180 type junitTestSuite struct { 181 XMLName xml.Name `xml:"testsuite"` 182 Name string `xml:"name,attr"` 183 Tests int `xml:"tests,attr"` 184 Skipped int `xml:"skipped,attr"` 185 Failures int `xml:"failures,attr"` 186 Errors int `xml:"errors,attr"` 187 Time string `xml:"time,attr"` 188 TestCases []*junitTestCase 189 } 190 191 func (ts *junitTestSuite) current() *junitTestCase { 192 return ts.TestCases[len(ts.TestCases)-1] 193 } 194 195 type junitPackageSuite struct { 196 XMLName xml.Name `xml:"testsuites"` 197 Name string `xml:"name,attr"` 198 Tests int `xml:"tests,attr"` 199 Skipped int `xml:"skipped,attr"` 200 Failures int `xml:"failures,attr"` 201 Errors int `xml:"errors,attr"` 202 Time string `xml:"time,attr"` 203 TestSuites []*junitTestSuite 204 } 205 206 func (j *junitFormatter) current() *junitTestSuite { 207 return j.suite.TestSuites[len(j.suite.TestSuites)-1] 208 }