github.com/lonnblad/godog@v0.7.14-0.20200306004719-1b0cb3259847/fmt_junit.go (about) 1 package godog 2 3 import ( 4 "encoding/xml" 5 "fmt" 6 "io" 7 "os" 8 "sort" 9 "strconv" 10 "time" 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{basefmt: newBaseFmt(suite, out)} 19 } 20 21 type junitFormatter struct { 22 *basefmt 23 } 24 25 func (f *junitFormatter) Summary() { 26 suite := buildJUNITPackageSuite(f.suiteName, f.started, f.features) 27 28 _, err := io.WriteString(f.out, xml.Header) 29 if err != nil { 30 fmt.Fprintln(os.Stderr, "failed to write junit string:", err) 31 } 32 33 enc := xml.NewEncoder(f.out) 34 enc.Indent("", s(2)) 35 if err = enc.Encode(suite); err != nil { 36 fmt.Fprintln(os.Stderr, "failed to write junit xml:", err) 37 } 38 } 39 40 func (f *junitFormatter) Sync(cf ConcurrentFormatter) { 41 if source, ok := cf.(*junitFormatter); ok { 42 f.basefmt.Sync(source.basefmt) 43 } 44 } 45 46 func (f *junitFormatter) Copy(cf ConcurrentFormatter) { 47 if source, ok := cf.(*junitFormatter); ok { 48 f.basefmt.Copy(source.basefmt) 49 } 50 } 51 52 func junitTimeDuration(from, to time.Time) string { 53 return strconv.FormatFloat(to.Sub(from).Seconds(), 'f', -1, 64) 54 } 55 56 func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*feature) junitPackageSuite { 57 suite := junitPackageSuite{ 58 Name: suiteName, 59 TestSuites: make([]*junitTestSuite, len(features)), 60 Time: junitTimeDuration(startedAt, timeNowFunc()), 61 } 62 63 sort.Sort(sortByName(features)) 64 65 for idx, feat := range features { 66 ts := junitTestSuite{ 67 Name: feat.GherkinDocument.Feature.Name, 68 Time: junitTimeDuration(feat.startedAt(), feat.finishedAt()), 69 TestCases: make([]*junitTestCase, len(feat.pickleResults)), 70 } 71 72 var testcaseNames = make(map[string]int) 73 for _, pickleResult := range feat.pickleResults { 74 testcaseNames[pickleResult.Name] = testcaseNames[pickleResult.Name] + 1 75 } 76 77 var outlineNo = make(map[string]int) 78 for idx, pickleResult := range feat.pickleResults { 79 tc := junitTestCase{} 80 tc.Time = junitTimeDuration(pickleResult.startedAt(), pickleResult.finishedAt()) 81 82 tc.Name = pickleResult.Name 83 if testcaseNames[tc.Name] > 1 { 84 outlineNo[tc.Name] = outlineNo[tc.Name] + 1 85 tc.Name += fmt.Sprintf(" #%d", outlineNo[tc.Name]) 86 } 87 88 ts.Tests++ 89 suite.Tests++ 90 91 for _, stepResult := range pickleResult.stepResults { 92 switch stepResult.status { 93 case passed: 94 tc.Status = passed.String() 95 case failed: 96 tc.Status = failed.String() 97 tc.Failure = &junitFailure{ 98 Message: fmt.Sprintf("Step %s: %s", stepResult.step.Text, stepResult.err), 99 } 100 case skipped: 101 tc.Error = append(tc.Error, &junitError{ 102 Type: "skipped", 103 Message: fmt.Sprintf("Step %s", stepResult.step.Text), 104 }) 105 case undefined: 106 tc.Status = undefined.String() 107 tc.Error = append(tc.Error, &junitError{ 108 Type: "undefined", 109 Message: fmt.Sprintf("Step %s", stepResult.step.Text), 110 }) 111 case pending: 112 tc.Status = pending.String() 113 tc.Error = append(tc.Error, &junitError{ 114 Type: "pending", 115 Message: fmt.Sprintf("Step %s: TODO: write pending definition", stepResult.step.Text), 116 }) 117 } 118 } 119 120 switch tc.Status { 121 case failed.String(): 122 ts.Failures++ 123 suite.Failures++ 124 case undefined.String(), pending.String(): 125 ts.Errors++ 126 suite.Errors++ 127 } 128 129 ts.TestCases[idx] = &tc 130 } 131 132 suite.TestSuites[idx] = &ts 133 } 134 135 return suite 136 } 137 138 type junitFailure struct { 139 Message string `xml:"message,attr"` 140 Type string `xml:"type,attr,omitempty"` 141 } 142 143 type junitError struct { 144 XMLName xml.Name `xml:"error,omitempty"` 145 Message string `xml:"message,attr"` 146 Type string `xml:"type,attr"` 147 } 148 149 type junitTestCase struct { 150 XMLName xml.Name `xml:"testcase"` 151 Name string `xml:"name,attr"` 152 Status string `xml:"status,attr"` 153 Time string `xml:"time,attr"` 154 Failure *junitFailure `xml:"failure,omitempty"` 155 Error []*junitError 156 } 157 158 type junitTestSuite struct { 159 XMLName xml.Name `xml:"testsuite"` 160 Name string `xml:"name,attr"` 161 Tests int `xml:"tests,attr"` 162 Skipped int `xml:"skipped,attr"` 163 Failures int `xml:"failures,attr"` 164 Errors int `xml:"errors,attr"` 165 Time string `xml:"time,attr"` 166 TestCases []*junitTestCase 167 } 168 169 type junitPackageSuite struct { 170 XMLName xml.Name `xml:"testsuites"` 171 Name string `xml:"name,attr"` 172 Tests int `xml:"tests,attr"` 173 Skipped int `xml:"skipped,attr"` 174 Failures int `xml:"failures,attr"` 175 Errors int `xml:"errors,attr"` 176 Time string `xml:"time,attr"` 177 TestSuites []*junitTestSuite 178 }