1
19
20 package reporter
21
22 import (
23 "encoding/xml"
24 "fmt"
25 "math"
26 "os"
27 "path/filepath"
28 "strings"
29
30 "github.com/onsi/ginkgo/v2/config"
31 "github.com/onsi/ginkgo/v2/types"
32 )
33
34 type JUnitTestCase struct {
35 Name string `xml:"name,attr"`
36 ClassName string `xml:"classname,attr"`
37 PassedMessage *JUnitPassedMessage `xml:"passed,omitempty"`
38 FailureMessage *JUnitFailureMessage `xml:"failure,omitempty"`
39 Skipped *JUnitSkipped `xml:"skipped,omitempty"`
40 Time float64 `xml:"time,attr"`
41 SystemOut string `xml:"system-out,omitempty"`
42 }
43
44 type JUnitPassedMessage struct {
45 Message string `xml:",chardata"`
46 }
47
48 type JUnitFailureMessage struct {
49 Type string `xml:"type,attr"`
50 Message string `xml:",chardata"`
51 }
52
53 type JUnitSkipped struct {
54 XMLName xml.Name `xml:"skipped"`
55 }
56
57 type JUnitTestSuite struct {
58 XMLName xml.Name `xml:"testsuite"`
59 TestCases []JUnitTestCase `xml:"testcase"`
60 Name string `xml:"name,attr"`
61 Tests int `xml:"tests,attr"`
62 Failures int `xml:"failures,attr"`
63 Errors int `xml:"errors,attr"`
64 Time float64 `xml:"time,attr"`
65 }
66 type V1JUnitReporter struct {
67 suite JUnitTestSuite
68 filename string
69 testSuiteName string
70 ReporterConfig config.DefaultReporterConfigType
71 }
72
73
74 func NewV1JUnitReporter(filename string) *V1JUnitReporter {
75 return &V1JUnitReporter{
76 filename: filename,
77 }
78 }
79
80 func (reporter *V1JUnitReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
81 reporter.handleSetupSummary("AfterSuite", setupSummary)
82 }
83
84 func (reporter *V1JUnitReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
85 reporter.handleSetupSummary("BeforeSuite", setupSummary)
86 }
87
88 func (reporter *V1JUnitReporter) handleSetupSummary(name string, setupSummary *types.SetupSummary) {
89 if setupSummary.State != types.SpecStatePassed {
90 testCase := JUnitTestCase{
91 Name: name,
92 ClassName: reporter.testSuiteName,
93 }
94
95 testCase.FailureMessage = &JUnitFailureMessage{
96 Type: reporter.failureTypeForState(setupSummary.State),
97 Message: failureMessage(setupSummary.Failure),
98 }
99 testCase.SystemOut = setupSummary.CapturedOutput
100 testCase.Time = setupSummary.RunTime.Seconds()
101 reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
102 }
103 }
104
105 func failureMessage(failure types.SpecFailure) string {
106 return fmt.Sprintf("%s\n%s\n%s", failure.ComponentCodeLocation.String(), failure.Message, failure.Location.String())
107 }
108
109 func (reporter *V1JUnitReporter) SpecDidComplete(specSummary *types.SpecSummary) {
110 testCase := JUnitTestCase{
111 Name: strings.Join(specSummary.ComponentTexts, " "),
112 ClassName: reporter.testSuiteName,
113 }
114 if reporter.ReporterConfig.ReportPassed && specSummary.State == types.SpecStatePassed {
115 testCase.PassedMessage = &JUnitPassedMessage{
116 Message: specSummary.CapturedOutput,
117 }
118 }
119 if specSummary.State == types.SpecStateFailed || specSummary.State == types.SpecStateInterrupted || specSummary.State == types.SpecStatePanicked {
120 testCase.FailureMessage = &JUnitFailureMessage{
121 Type: reporter.failureTypeForState(specSummary.State),
122 Message: failureMessage(specSummary.Failure),
123 }
124 if specSummary.State == types.SpecStatePanicked {
125 testCase.FailureMessage.Message += fmt.Sprintf("\n\nPanic: %s\n\nFull stack:\n%s",
126 specSummary.Failure.ForwardedPanic,
127 specSummary.Failure.Location.FullStackTrace)
128 }
129 testCase.SystemOut = specSummary.CapturedOutput
130 }
131 if specSummary.State == types.SpecStateSkipped || specSummary.State == types.SpecStatePending {
132 testCase.Skipped = &JUnitSkipped{}
133 }
134 testCase.Time = specSummary.RunTime.Seconds()
135 reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
136 }
137
138 func (reporter *V1JUnitReporter) SpecWillRun(specSummary *types.SpecSummary) {
139 }
140
141 func (reporter *V1JUnitReporter) SuiteDidEnd(summary *types.SuiteSummary) {
142 reporter.suite.Tests = summary.NumberOfSpecsThatWillBeRun
143 reporter.suite.Time = math.Trunc(summary.RunTime.Seconds()*1000) / 1000
144 reporter.suite.Failures = summary.NumberOfFailedSpecs
145 reporter.suite.Errors = 0
146 if reporter.ReporterConfig.ReportFile != "" {
147 reporter.filename = reporter.ReporterConfig.ReportFile
148 fmt.Printf("\nJUnit path was configured: %s\n", reporter.filename)
149 }
150 filePath, _ := filepath.Abs(reporter.filename)
151 dirPath := filepath.Dir(filePath)
152 err := os.MkdirAll(dirPath, os.ModePerm)
153 if err != nil {
154 fmt.Printf("\nFailed to create JUnit directory: %s\n\t%s", filePath, err.Error())
155 }
156 file, err := os.Create(filePath)
157 if err != nil {
158 fmt.Fprintf(os.Stderr, "Failed to create JUnit report file: %s\n\t%s", filePath, err.Error())
159 }
160 defer file.Close()
161 file.WriteString(xml.Header)
162 encoder := xml.NewEncoder(file)
163 encoder.Indent(" ", " ")
164 err = encoder.Encode(reporter.suite)
165 if err == nil {
166 fmt.Fprintf(os.Stdout, "\nJUnit report was created: %s\n", filePath)
167 } else {
168 fmt.Fprintf(os.Stderr, "\nFailed to generate JUnit report data:\n\t%s", err.Error())
169 }
170 }
171
172 func (reporter *V1JUnitReporter) SuiteWillBegin(ginkgoConfig config.GinkgoConfigType, summary *types.SuiteSummary) {
173 reporter.suite = JUnitTestSuite{
174 Name: summary.SuiteDescription,
175 TestCases: []JUnitTestCase{},
176 }
177 reporter.testSuiteName = summary.SuiteDescription
178 reporter.ReporterConfig = config.DefaultReporterConfigType{}
179 }
180
181 func (reporter *V1JUnitReporter) failureTypeForState(state types.SpecState) string {
182 switch state {
183 case types.SpecStateFailed:
184 return "Failure"
185 case types.SpecStateInterrupted:
186 return "Interrupted"
187 case types.SpecStatePanicked:
188 return "Panic"
189 default:
190 return ""
191 }
192 }
193
View as plain text