Skip to content

Commit 8f3bd70

Browse files
committedJan 17, 2024
JUnit reports now interpret Label(owner:X) and set owner to X.
1 parent dbaf18f commit 8f3bd70

File tree

3 files changed

+23
-7
lines changed

3 files changed

+23
-7
lines changed
 

‎docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3389,7 +3389,7 @@ Ginkgo also supports generating JUnit reports with
33893389
ginkgo --junit-report=report.xml
33903390
```
33913391

3392-
The JUnit report is compatible with the JUnit specification, however Ginkgo specs carry much more metadata than can be easily mapped onto the JUnit spec so some information is lost and/or a bit harder to decode than using Ginkgo's native JSON format.
3392+
The JUnit report is compatible with the JUnit specification, however Ginkgo specs carry much more metadata than can be easily mapped onto the JUnit spec so some information is lost and/or a bit harder to decode than using Ginkgo's native JSON format. Nonetheless, Ginkgo does its best to populate as much of the JUnit report as possible. This includes adding additional metadata using [labels](#spec-labels) - in particular if you provide a label of the form `Label("owner:XYZ")`, the generating JUnit spec will set the `Owner` attribute to `XYZ`.
33933393

33943394
Ginkgo also supports Teamcity reports with `ginkgo --teamcity-report=report.teamcity` though, again, the Teamcity spec makes it difficult to capture all the spec metadata.
33953395

‎reporters/junit_report.go

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"fmt"
1616
"os"
1717
"path"
18+
"regexp"
1819
"strings"
1920

2021
"github.com/onsi/ginkgo/v2/config"
@@ -104,6 +105,8 @@ type JUnitProperty struct {
104105
Value string `xml:"value,attr"`
105106
}
106107

108+
var ownerRE = regexp.MustCompile(`(?i)^owner:(.*)$`)
109+
107110
type JUnitTestCase struct {
108111
// Name maps onto the full text of the spec - equivalent to "[SpecReport.LeafNodeType] SpecReport.FullText()"
109112
Name string `xml:"name,attr"`
@@ -113,6 +116,8 @@ type JUnitTestCase struct {
113116
Status string `xml:"status,attr"`
114117
// Time is the time in seconds to execute the spec - maps onto SpecReport.RunTime
115118
Time float64 `xml:"time,attr"`
119+
// Owner is the owner the spec - is set if a label matching Label("owner:X") is provided
120+
Owner string `xml:"owner,attr,omitempty"`
116121
//Skipped is populated with a message if the test was skipped or pending
117122
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
118123
//Error is populated if the test panicked or was interrupted
@@ -195,13 +200,20 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
195200
if len(labels) > 0 && !config.OmitSpecLabels {
196201
name = name + " [" + strings.Join(labels, ", ") + "]"
197202
}
203+
owner := ""
204+
for _, label := range labels {
205+
if matches := ownerRE.FindStringSubmatch(label); len(matches) == 2 {
206+
owner = matches[1]
207+
}
208+
}
198209
name = strings.TrimSpace(name)
199210

200211
test := JUnitTestCase{
201212
Name: name,
202213
Classname: report.SuiteDescription,
203214
Status: spec.State.String(),
204215
Time: spec.RunTime.Seconds(),
216+
Owner: owner,
205217
}
206218
if !spec.State.Is(config.OmitTimelinesForSpecState) {
207219
test.SystemErr = systemErrForUnstructuredReporters(spec)

‎reporters/junit_report_test.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var _ = Describe("JunitReport", func() {
3333
RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever),
3434
AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")),
3535
),
36-
S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"),
36+
S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), Label("cat", "owner:frank", "OWNer:bob"),
3737
SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0),
3838
PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")),
3939
SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")),
@@ -42,8 +42,8 @@ var _ = Describe("JunitReport", func() {
4242
SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")),
4343
SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")),
4444
),
45-
S(types.NodeTypeIt, "A", cl0, types.SpecStatePending),
46-
S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, STD("some captured stdout\n"),
45+
S(types.NodeTypeIt, "A", cl0, types.SpecStatePending, CLabels(Label("owner:org")), Label("owner:team")),
46+
S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, CLabels(Label("owner:org")), STD("some captured stdout\n"),
4747
SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0),
4848
F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")),
4949
SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")),
@@ -95,6 +95,7 @@ var _ = Describe("JunitReport", func() {
9595
Ω(failingSpec.Status).Should(Equal("timedout"))
9696
Ω(failingSpec.Skipped).Should(BeNil())
9797
Ω(failingSpec.Error).Should(BeNil())
98+
Ω(failingSpec.Owner).Should(Equal(""))
9899
Ω(failingSpec.Failure.Message).Should(Equal("failure\nmessage"))
99100
Ω(failingSpec.Failure.Type).Should(Equal("timedout"))
100101
Ω(failingSpec.Failure.Description).Should(MatchLines(
@@ -141,12 +142,13 @@ var _ = Describe("JunitReport", func() {
141142
))
142143

143144
passingSpec := suite.TestCases[1]
144-
Ω(passingSpec.Name).Should(Equal("[It] A"))
145+
Ω(passingSpec.Name).Should(Equal("[It] A [cat, owner:frank, OWNer:bob]"))
145146
Ω(passingSpec.Classname).Should(Equal("My Suite"))
146147
Ω(passingSpec.Status).Should(Equal("passed"))
147148
Ω(passingSpec.Skipped).Should(BeNil())
148149
Ω(passingSpec.Error).Should(BeNil())
149150
Ω(passingSpec.Failure).Should(BeNil())
151+
Ω(passingSpec.Owner).Should(Equal("bob"))
150152
Ω(passingSpec.SystemOut).Should(Equal("some captured stdout\n"))
151153
Ω(passingSpec.SystemErr).Should(MatchLines(
152154
spr("> Enter [It] A - cl0.go:12 @ %s", FORMATTED_TIME),
@@ -165,20 +167,22 @@ var _ = Describe("JunitReport", func() {
165167
))
166168

167169
pendingSpec := suite.TestCases[2]
168-
Ω(pendingSpec.Name).Should(Equal("[It] A"))
170+
Ω(pendingSpec.Name).Should(Equal("[It] A [owner:org, owner:team]"))
169171
Ω(pendingSpec.Classname).Should(Equal("My Suite"))
170172
Ω(pendingSpec.Status).Should(Equal("pending"))
171173
Ω(pendingSpec.Skipped.Message).Should(Equal("pending"))
172174
Ω(pendingSpec.Error).Should(BeNil())
175+
Ω(pendingSpec.Owner).Should(Equal("team"))
173176
Ω(pendingSpec.Failure).Should(BeNil())
174177
Ω(pendingSpec.SystemOut).Should(BeEmpty())
175178
Ω(pendingSpec.SystemErr).Should(BeEmpty())
176179

177180
panickedSpec := suite.TestCases[3]
178-
Ω(panickedSpec.Name).Should(Equal("[It] A"))
181+
Ω(panickedSpec.Name).Should(Equal("[It] A [owner:org]"))
179182
Ω(panickedSpec.Classname).Should(Equal("My Suite"))
180183
Ω(panickedSpec.Status).Should(Equal("panicked"))
181184
Ω(panickedSpec.Skipped).Should(BeNil())
185+
Ω(panickedSpec.Owner).Should(Equal("org"))
182186
Ω(panickedSpec.Error.Message).Should(Equal("the panic"))
183187
Ω(panickedSpec.Error.Type).Should(Equal("panicked"))
184188
Ω(panickedSpec.Error.Description).Should(MatchLines(

0 commit comments

Comments
 (0)
Please sign in to comment.