github.com/MarianMacik/godog@v0.7.9/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 _, err := io.WriteString(j.out, xml.Header) 152 if err != nil { 153 fmt.Fprintln(os.Stderr, "failed to write junit string:", err) 154 } 155 enc := xml.NewEncoder(j.out) 156 enc.Indent("", s(2)) 157 if err = enc.Encode(j.suite); err != nil { 158 fmt.Fprintln(os.Stderr, "failed to write junit xml:", err) 159 } 160 } 161 162 type junitFailure struct { 163 Message string `xml:"message,attr"` 164 Type string `xml:"type,attr,omitempty"` 165 } 166 167 type junitError struct { 168 XMLName xml.Name `xml:"error,omitempty"` 169 Message string `xml:"message,attr"` 170 Type string `xml:"type,attr"` 171 } 172 173 type junitTestCase struct { 174 XMLName xml.Name `xml:"testcase"` 175 Name string `xml:"name,attr"` 176 Status string `xml:"status,attr"` 177 Time string `xml:"time,attr"` 178 Failure *junitFailure `xml:"failure,omitempty"` 179 Error []*junitError 180 } 181 182 type junitTestSuite struct { 183 XMLName xml.Name `xml:"testsuite"` 184 Name string `xml:"name,attr"` 185 Tests int `xml:"tests,attr"` 186 Skipped int `xml:"skipped,attr"` 187 Failures int `xml:"failures,attr"` 188 Errors int `xml:"errors,attr"` 189 Time string `xml:"time,attr"` 190 TestCases []*junitTestCase 191 } 192 193 func (ts *junitTestSuite) current() *junitTestCase { 194 return ts.TestCases[len(ts.TestCases)-1] 195 } 196 197 type junitPackageSuite struct { 198 XMLName xml.Name `xml:"testsuites"` 199 Name string `xml:"name,attr"` 200 Tests int `xml:"tests,attr"` 201 Skipped int `xml:"skipped,attr"` 202 Failures int `xml:"failures,attr"` 203 Errors int `xml:"errors,attr"` 204 Time string `xml:"time,attr"` 205 TestSuites []*junitTestSuite 206 } 207 208 func (j *junitFormatter) current() *junitTestSuite { 209 return j.suite.TestSuites[len(j.suite.TestSuites)-1] 210 }