Skip to content

Commit

Permalink
JUnit reports now interpret Label(owner:X) and set owner to X.
Browse files Browse the repository at this point in the history
  • Loading branch information
onsi committed Jan 17, 2024
1 parent dbaf18f commit 8f3bd70
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 7 deletions.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3389,7 +3389,7 @@ Ginkgo also supports generating JUnit reports with
ginkgo --junit-report=report.xml
```

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.
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`.

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.

Expand Down
12 changes: 12 additions & 0 deletions reporters/junit_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"fmt"
"os"
"path"
"regexp"
"strings"

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

var ownerRE = regexp.MustCompile(`(?i)^owner:(.*)$`)

type JUnitTestCase struct {
// Name maps onto the full text of the spec - equivalent to "[SpecReport.LeafNodeType] SpecReport.FullText()"
Name string `xml:"name,attr"`
Expand All @@ -113,6 +116,8 @@ type JUnitTestCase struct {
Status string `xml:"status,attr"`
// Time is the time in seconds to execute the spec - maps onto SpecReport.RunTime
Time float64 `xml:"time,attr"`
// Owner is the owner the spec - is set if a label matching Label("owner:X") is provided
Owner string `xml:"owner,attr,omitempty"`
//Skipped is populated with a message if the test was skipped or pending
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
//Error is populated if the test panicked or was interrupted
Expand Down Expand Up @@ -195,13 +200,20 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
if len(labels) > 0 && !config.OmitSpecLabels {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
owner := ""
for _, label := range labels {
if matches := ownerRE.FindStringSubmatch(label); len(matches) == 2 {
owner = matches[1]
}
}
name = strings.TrimSpace(name)

test := JUnitTestCase{
Name: name,
Classname: report.SuiteDescription,
Status: spec.State.String(),
Time: spec.RunTime.Seconds(),
Owner: owner,
}
if !spec.State.Is(config.OmitTimelinesForSpecState) {
test.SystemErr = systemErrForUnstructuredReporters(spec)
Expand Down
16 changes: 10 additions & 6 deletions reporters/junit_report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var _ = Describe("JunitReport", func() {
RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever),
AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")),
),
S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"),
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"),
SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0),
PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")),
SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")),
Expand All @@ -42,8 +42,8 @@ var _ = Describe("JunitReport", func() {
SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")),
SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")),
),
S(types.NodeTypeIt, "A", cl0, types.SpecStatePending),
S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, STD("some captured stdout\n"),
S(types.NodeTypeIt, "A", cl0, types.SpecStatePending, CLabels(Label("owner:org")), Label("owner:team")),
S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, CLabels(Label("owner:org")), STD("some captured stdout\n"),
SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0),
F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")),
SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")),
Expand Down Expand Up @@ -95,6 +95,7 @@ var _ = Describe("JunitReport", func() {
Ω(failingSpec.Status).Should(Equal("timedout"))
Ω(failingSpec.Skipped).Should(BeNil())
Ω(failingSpec.Error).Should(BeNil())
Ω(failingSpec.Owner).Should(Equal(""))
Ω(failingSpec.Failure.Message).Should(Equal("failure\nmessage"))
Ω(failingSpec.Failure.Type).Should(Equal("timedout"))
Ω(failingSpec.Failure.Description).Should(MatchLines(
Expand Down Expand Up @@ -141,12 +142,13 @@ var _ = Describe("JunitReport", func() {
))

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

pendingSpec := suite.TestCases[2]
Ω(pendingSpec.Name).Should(Equal("[It] A"))
Ω(pendingSpec.Name).Should(Equal("[It] A [owner:org, owner:team]"))
Ω(pendingSpec.Classname).Should(Equal("My Suite"))
Ω(pendingSpec.Status).Should(Equal("pending"))
Ω(pendingSpec.Skipped.Message).Should(Equal("pending"))
Ω(pendingSpec.Error).Should(BeNil())
Ω(pendingSpec.Owner).Should(Equal("team"))
Ω(pendingSpec.Failure).Should(BeNil())
Ω(pendingSpec.SystemOut).Should(BeEmpty())
Ω(pendingSpec.SystemErr).Should(BeEmpty())

panickedSpec := suite.TestCases[3]
Ω(panickedSpec.Name).Should(Equal("[It] A"))
Ω(panickedSpec.Name).Should(Equal("[It] A [owner:org]"))
Ω(panickedSpec.Classname).Should(Equal("My Suite"))
Ω(panickedSpec.Status).Should(Equal("panicked"))
Ω(panickedSpec.Skipped).Should(BeNil())
Ω(panickedSpec.Owner).Should(Equal("org"))
Ω(panickedSpec.Error.Message).Should(Equal("the panic"))
Ω(panickedSpec.Error.Type).Should(Equal("panicked"))
Ω(panickedSpec.Error.Description).Should(MatchLines(
Expand Down

0 comments on commit 8f3bd70

Please sign in to comment.