Skip to content

Commit 4177485

Browse files
sebhosszegl
authored andcommitted
render/junit: add new output
This produces JUnit XML compatible output when the 'junit' output format is specified. Fixes #459 Signed-off-by: Sebastian Hoß <[email protected]>
1 parent fb19eb5 commit 4177485

File tree

5 files changed

+226
-0
lines changed

5 files changed

+226
-0
lines changed

cmd/kube-score/main.go

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/zegl/kube-score/renderer/ci"
1919
"github.com/zegl/kube-score/renderer/human"
2020
"github.com/zegl/kube-score/renderer/json_v2"
21+
"github.com/zegl/kube-score/renderer/junit"
2122
"github.com/zegl/kube-score/renderer/sarif"
2223
"github.com/zegl/kube-score/score"
2324
"github.com/zegl/kube-score/score/checks"
@@ -255,6 +256,8 @@ Use "-" as filename to read from STDIN.`, execName(binName))
255256
r = ci.CI(scoreCard)
256257
case *outputFormat == "sarif":
257258
r = sarif.Output(scoreCard)
259+
case *outputFormat == "junit":
260+
r = junit.JUnit(scoreCard)
258261
default:
259262
return fmt.Errorf("error: Unknown --output-format or --output-version")
260263
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ require (
44
github.com/eidolon/wordwrap v0.0.0-20161011182207-e0f54129b8bb
55
github.com/fatih/color v1.17.0
66
github.com/google/go-cmp v0.6.0
7+
github.com/jstemmer/go-junit-report/v2 v2.1.0
78
github.com/mattn/go-isatty v0.0.20
89
github.com/spf13/pflag v1.0.5
910
github.com/stretchr/testify v1.9.0

go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
99
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
1010
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
1111
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
12+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1213
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1314
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
1415
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -17,6 +18,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
1718
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
1819
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
1920
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
21+
github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc=
22+
github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
2023
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
2124
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
2225
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

renderer/junit/junit.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package junit
2+
3+
import (
4+
"bytes"
5+
"github.com/jstemmer/go-junit-report/v2/junit"
6+
"github.com/zegl/kube-score/scorecard"
7+
"io"
8+
)
9+
10+
// JUnit XML output
11+
func JUnit(scoreCard *scorecard.Scorecard) io.Reader {
12+
testSuites := junit.Testsuites{
13+
Name: "kube-score",
14+
}
15+
16+
for _, scoredObject := range *scoreCard {
17+
testsuite := junit.Testsuite{
18+
Name: scoredObject.HumanFriendlyRef(),
19+
}
20+
21+
for _, testScore := range scoredObject.Checks {
22+
if len(testScore.Comments) == 0 {
23+
if testScore.Skipped {
24+
testsuite.AddTestcase(junit.Testcase{
25+
Name: testScore.Check.Name,
26+
Classname: scoredObject.HumanFriendlyRef(),
27+
Skipped: &junit.Result{},
28+
})
29+
} else {
30+
if testScore.Grade == scorecard.GradeAlmostOK || testScore.Grade == scorecard.GradeAllOK {
31+
testsuite.AddTestcase(junit.Testcase{
32+
Name: testScore.Check.Name,
33+
Classname: scoredObject.HumanFriendlyRef(),
34+
})
35+
} else {
36+
testsuite.AddTestcase(junit.Testcase{
37+
Name: testScore.Check.Name,
38+
Classname: scoredObject.HumanFriendlyRef(),
39+
Failure: &junit.Result{},
40+
})
41+
}
42+
}
43+
} else {
44+
for _, comment := range testScore.Comments {
45+
message := comment.Summary
46+
if comment.Path != "" {
47+
message = "(" + comment.Path + ") " + comment.Summary
48+
}
49+
50+
if testScore.Skipped {
51+
testsuite.AddTestcase(junit.Testcase{
52+
Name: testScore.Check.Name,
53+
Classname: scoredObject.HumanFriendlyRef(),
54+
Skipped: &junit.Result{
55+
Message: message,
56+
},
57+
})
58+
} else {
59+
if testScore.Grade == scorecard.GradeAlmostOK || testScore.Grade == scorecard.GradeAllOK {
60+
testsuite.AddTestcase(junit.Testcase{
61+
Name: testScore.Check.Name,
62+
Classname: scoredObject.HumanFriendlyRef(),
63+
})
64+
} else {
65+
testsuite.AddTestcase(junit.Testcase{
66+
Name: testScore.Check.Name,
67+
Classname: scoredObject.HumanFriendlyRef(),
68+
Failure: &junit.Result{
69+
Message: message,
70+
},
71+
})
72+
}
73+
74+
}
75+
}
76+
}
77+
}
78+
testSuites.AddSuite(testsuite)
79+
}
80+
81+
buffer := &bytes.Buffer{}
82+
err := testSuites.WriteXML(buffer)
83+
if err != nil {
84+
panic(err)
85+
}
86+
return buffer
87+
}

renderer/junit/junit_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package junit
2+
3+
import (
4+
"io"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/zegl/kube-score/domain"
9+
"github.com/zegl/kube-score/scorecard"
10+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
)
12+
13+
func getTestCard() *scorecard.Scorecard {
14+
checks := []scorecard.TestScore{
15+
{
16+
Check: domain.Check{
17+
Name: "test-warning-two-comments",
18+
},
19+
Grade: scorecard.GradeWarning,
20+
Comments: []scorecard.TestScoreComment{
21+
{
22+
Path: "a",
23+
Summary: "summary",
24+
Description: "description",
25+
},
26+
{
27+
// No path
28+
Summary: "summary",
29+
Description: "description",
30+
},
31+
},
32+
},
33+
{
34+
Check: domain.Check{
35+
Name: "test-ok-comment",
36+
},
37+
Grade: scorecard.GradeAllOK,
38+
Comments: []scorecard.TestScoreComment{
39+
{
40+
Path: "a",
41+
Summary: "summary",
42+
Description: "description",
43+
},
44+
},
45+
},
46+
{
47+
Check: domain.Check{
48+
Name: "test-skipped-comment",
49+
},
50+
Skipped: true,
51+
Comments: []scorecard.TestScoreComment{
52+
{
53+
Path: "a",
54+
Summary: "skipped sum",
55+
Description: "skipped description",
56+
},
57+
},
58+
},
59+
{
60+
Check: domain.Check{
61+
Name: "test-skipped-no-comment",
62+
},
63+
Skipped: true,
64+
},
65+
}
66+
67+
return &scorecard.Scorecard{
68+
"a": &scorecard.ScoredObject{
69+
TypeMeta: v1.TypeMeta{
70+
Kind: "Testing",
71+
APIVersion: "v1",
72+
},
73+
ObjectMeta: v1.ObjectMeta{
74+
Name: "foo",
75+
Namespace: "foofoo",
76+
},
77+
Checks: checks,
78+
},
79+
80+
// No namespace
81+
"b": &scorecard.ScoredObject{
82+
TypeMeta: v1.TypeMeta{
83+
Kind: "Testing",
84+
APIVersion: "v1",
85+
},
86+
ObjectMeta: v1.ObjectMeta{
87+
Name: "bar-no-namespace",
88+
},
89+
Checks: checks,
90+
},
91+
}
92+
}
93+
94+
func TestJUnitOutput(t *testing.T) {
95+
t.Parallel()
96+
r := JUnit(getTestCard())
97+
all, err := io.ReadAll(r)
98+
assert.Nil(t, err)
99+
assert.Equal(t, `<testsuites name="kube-score" tests="10" failures="4" skipped="4">
100+
<testsuite name="foo/foofoo v1/Testing" tests="5" failures="2" errors="0" id="0" skipped="2" time="">
101+
<testcase name="test-warning-two-comments" classname="foo/foofoo v1/Testing">
102+
<failure message="(a) summary"></failure>
103+
</testcase>
104+
<testcase name="test-warning-two-comments" classname="foo/foofoo v1/Testing">
105+
<failure message="summary"></failure>
106+
</testcase>
107+
<testcase name="test-ok-comment" classname="foo/foofoo v1/Testing"></testcase>
108+
<testcase name="test-skipped-comment" classname="foo/foofoo v1/Testing">
109+
<skipped message="(a) skipped sum"></skipped>
110+
</testcase>
111+
<testcase name="test-skipped-no-comment" classname="foo/foofoo v1/Testing">
112+
<skipped message=""></skipped>
113+
</testcase>
114+
</testsuite>
115+
<testsuite name="bar-no-namespace v1/Testing" tests="5" failures="2" errors="0" id="0" skipped="2" time="">
116+
<testcase name="test-warning-two-comments" classname="bar-no-namespace v1/Testing">
117+
<failure message="(a) summary"></failure>
118+
</testcase>
119+
<testcase name="test-warning-two-comments" classname="bar-no-namespace v1/Testing">
120+
<failure message="summary"></failure>
121+
</testcase>
122+
<testcase name="test-ok-comment" classname="bar-no-namespace v1/Testing"></testcase>
123+
<testcase name="test-skipped-comment" classname="bar-no-namespace v1/Testing">
124+
<skipped message="(a) skipped sum"></skipped>
125+
</testcase>
126+
<testcase name="test-skipped-no-comment" classname="bar-no-namespace v1/Testing">
127+
<skipped message=""></skipped>
128+
</testcase>
129+
</testsuite>
130+
</testsuites>
131+
`, string(all))
132+
}

0 commit comments

Comments
 (0)