Skip to content

Commit 6538148

Browse files
committed
Add Junit output for automated results processing
If specified by a the new --junit flag, output the results as junit to the specified file. This allows automated processing. Updated sonobuoy plugin to use this junit format so that you can see result counts and test pass/fail info using native sonobuoy commands.
1 parent e0b5119 commit 6538148

10 files changed

+159
-26
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.16
55
require (
66
github.com/go-resty/resty/v2 v2.5.0
77
github.com/imdario/mergo v0.3.11 // indirect
8+
github.com/jstemmer/go-junit-report v0.9.1
89
github.com/olekukonko/tablewriter v0.0.4
910
github.com/onsi/ginkgo v1.11.0
1011
github.com/onsi/gomega v1.7.0

go.sum

+1-13
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
121121
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
122122
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
123123
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
124-
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
125124
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
126125
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
127126
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
@@ -164,6 +163,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
164163
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
165164
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
166165
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
166+
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
167167
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
168168
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
169169
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -174,11 +174,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ
174174
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
175175
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
176176
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
177-
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
178177
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
179178
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
180179
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
181-
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
182180
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
183181
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
184182
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -255,7 +253,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
255253
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
256254
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
257255
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
258-
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
259256
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
260257
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
261258
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -279,9 +276,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
279276
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
280277
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
281278
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
282-
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
283279
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
284-
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
285280
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
286281
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
287282
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -339,7 +334,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
339334
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
340335
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
341336
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
342-
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
343337
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
344338
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
345339
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -383,7 +377,6 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
383377
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
384378
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
385379
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
386-
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
387380
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
388381
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
389382
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -395,7 +388,6 @@ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fq
395388
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
396389
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
397390
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
398-
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
399391
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
400392
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
401393
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -440,7 +432,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
440432
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
441433
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
442434
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
443-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
444435
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
445436
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
446437
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -500,7 +491,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
500491
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
501492
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
502493
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
503-
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
504494
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
505495
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
506496
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -516,7 +506,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
516506
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
517507
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
518508
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
519-
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
520509
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
521510
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
522511
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -544,7 +533,6 @@ k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
544533
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
545534
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
546535
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
547-
sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8=
548536
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
549537
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
550538
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=

hack/sonobuoy/cyclonus-plugin.yaml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
sonobuoy-config:
22
driver: Job
33
plugin-name: cyclonus
4-
result-format: raw
4+
result-format: junit
55
spec:
66
command:
77
- ./run-sonobuoy-plugin.sh
88
- generate
99
- "--include=conflict"
1010
- "--exclude=egress,direction"
11+
- "--junit-results-file=/tmp/results/junit.xml"
1112
image: mfenwick100/sonobuoy-cyclonus:latest
12-
imagePullPolicy: IfNotPresent
13+
imagePullPolicy: Always
1314
name: plugin
1415
resources: {}
1516
volumeMounts:

hack/sonobuoy/run-sonobuoy-plugin.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ RESULTS_DIR="${RESULTS_DIR:-/tmp/results}"
1313
cd "${RESULTS_DIR}"
1414

1515
# Sonobuoy worker expects a tar file.
16-
tar czf results.tar.gz results.txt
16+
tar czf results.tar.gz ./*
1717

1818
# Signal to the worker that we are done and where to find the results.
1919
realpath results.tar.gz > ./done

pkg/cli/generate.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package cli
22

33
import (
44
"fmt"
5+
"strings"
6+
57
"github.com/mattfenwick/cyclonus/pkg/connectivity"
68
"github.com/mattfenwick/cyclonus/pkg/connectivity/probe"
79
"github.com/mattfenwick/cyclonus/pkg/generator"
810
"github.com/mattfenwick/cyclonus/pkg/kube"
911
"github.com/mattfenwick/cyclonus/pkg/utils"
1012
"github.com/sirupsen/logrus"
1113
"github.com/spf13/cobra"
12-
"strings"
1314
)
1415

1516
type GenerateArgs struct {
@@ -32,6 +33,7 @@ type GenerateArgs struct {
3233
Mock bool
3334
DryRun bool
3435
JobTimeoutSeconds int
36+
JunitResultsFile string
3537
}
3638

3739
func SetupGenerateCommand() *cobra.Command {
@@ -70,6 +72,8 @@ func SetupGenerateCommand() *cobra.Command {
7072
command.Flags().BoolVar(&args.Mock, "mock", false, "if true, use a mock kube runner (i.e. don't actually run tests against kubernetes; instead, product fake results")
7173
command.Flags().BoolVar(&args.DryRun, "dry-run", false, "if true, don't actually do anything: just print out what would be done")
7274

75+
command.Flags().StringVar(&args.JunitResultsFile, "junit-results-file", "", "output junit results to the specified file")
76+
7377
return command
7478
}
7579

@@ -110,8 +114,9 @@ func RunGenerateCommand(args *GenerateArgs) {
110114
}
111115
interpreter := connectivity.NewInterpreter(kubernetes, resources, interpreterConfig)
112116
printer := &connectivity.Printer{
113-
Noisy: args.Noisy,
114-
IgnoreLoopback: args.IgnoreLoopback,
117+
Noisy: args.Noisy,
118+
IgnoreLoopback: args.IgnoreLoopback,
119+
JunitResultsFile: args.JunitResultsFile,
115120
}
116121

117122
zcPod, err := resources.GetPod("z", "c")

pkg/connectivity/printer.go

+74-7
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
package connectivity
22

33
import (
4+
"encoding/xml"
45
"fmt"
6+
"io"
7+
"math"
8+
"os"
9+
"sort"
10+
"strconv"
11+
"strings"
12+
13+
junit "github.com/jstemmer/go-junit-report/formatter"
514
"github.com/mattfenwick/cyclonus/pkg/generator"
615
"github.com/mattfenwick/cyclonus/pkg/utils"
716
"github.com/olekukonko/tablewriter"
817
"github.com/pkg/errors"
18+
"github.com/sirupsen/logrus"
919
v1 "k8s.io/api/core/v1"
1020
networkingv1 "k8s.io/api/networking/v1"
1121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12-
"math"
1322
"sigs.k8s.io/yaml"
14-
"sort"
15-
"strings"
1623
)
1724

1825
type Printer struct {
19-
Noisy bool
20-
IgnoreLoopback bool
21-
Results []*Result
26+
Noisy bool
27+
IgnoreLoopback bool
28+
JunitResultsFile string
29+
Results []*Result
2230
}
2331

2432
func (t *Printer) PrintSummary() {
2533
summary := (&CombinedResults{Results: t.Results}).Summary(t.IgnoreLoopback)
26-
2734
t.printTestSummary(summary.Tests)
2835
for primary, counts := range summary.TagCounts {
2936
fmt.Println(passFailTable(primary, counts, nil, nil))
@@ -32,6 +39,66 @@ func (t *Printer) PrintSummary() {
3239

3340
fmt.Printf("Feature results:\n%s\n\n", t.printMarkdownFeatureTable(summary.FeaturePrimaryCounts, summary.FeatureCounts))
3441
fmt.Printf("Tag results:\n%s\n", t.printMarkdownFeatureTable(summary.TagPrimaryCounts, summary.TagCounts))
42+
if t.JunitResultsFile != "" {
43+
f, err := os.Create(t.JunitResultsFile)
44+
if err != nil {
45+
logrus.Errorf("Unable to create file %q for junit output: %v\n", t.JunitResultsFile, err)
46+
} else {
47+
defer f.Close()
48+
if err := printJunit(f, summary); err != nil {
49+
logrus.Errorf("Unable to write junit output: %v\n", err)
50+
}
51+
}
52+
}
53+
}
54+
55+
func printJunit(w io.Writer, summary *Summary) error {
56+
s := summaryToJunit(summary)
57+
enc := xml.NewEncoder(w)
58+
return enc.Encode(s)
59+
}
60+
61+
func summaryToJunit(summary *Summary) junit.JUnitTestSuite {
62+
s := junit.JUnitTestSuite{
63+
Name: "cyclonus",
64+
Failures: summary.Failed,
65+
TestCases: []junit.JUnitTestCase{},
66+
}
67+
68+
for _, testStrings := range summary.Tests {
69+
_, testName, passed := parseTestStrings(testStrings)
70+
// Only cases where the testname are non-empty are new tests, otherwise it
71+
// is multi-line output of the test.
72+
if testName == "" {
73+
continue
74+
}
75+
tc := junit.JUnitTestCase{
76+
Name: testName,
77+
}
78+
if !passed {
79+
tc.Failure = &junit.JUnitFailure{}
80+
}
81+
s.TestCases = append(s.TestCases, tc)
82+
}
83+
return s
84+
}
85+
86+
func parseTestStrings(input []string) (testNumber int, testName string, passed bool) {
87+
split := strings.SplitN(input[0], ": ", 2)
88+
if len(split) < 2 {
89+
return 0, "", false
90+
}
91+
92+
testNumber, err := strconv.Atoi(split[0])
93+
if err != nil {
94+
logrus.Errorf("error parsing test number from string %q for junit: %v", split[0], err)
95+
}
96+
97+
if len(input) > 1 && input[1] == "passed" {
98+
passed = true
99+
}
100+
101+
return testNumber, split[1], passed
35102
}
36103

37104
const (

pkg/connectivity/printer_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package connectivity
2+
3+
import (
4+
"bytes"
5+
"flag"
6+
"io/ioutil"
7+
"testing"
8+
)
9+
10+
var update = flag.Bool("update", false, "update .golden files")
11+
12+
func TestPrintJunit(t *testing.T) {
13+
testCases := []struct {
14+
desc string
15+
summary *Summary
16+
expectFile string
17+
expectErr string
18+
}{
19+
{
20+
desc: "Empty summary",
21+
summary: &Summary{},
22+
expectFile: "testdata/empty-summary.golden.xml",
23+
}, {
24+
desc: "2 pass 2 fail",
25+
summary: &Summary{
26+
Tests: [][]string{
27+
{"1: test1", "passed", ""},
28+
{"2: test2 with spaces", "failed", ""},
29+
{"3: test3 with + special %chars/", "passed", ""},
30+
{"4: test4 with\nnewlines", "foo-is-failed", ""},
31+
},
32+
},
33+
expectFile: "testdata/2-pass-2-fail.golden.xml",
34+
}, {
35+
desc: "Uses failure count from summary",
36+
summary: &Summary{
37+
Failed: 10,
38+
},
39+
expectFile: "testdata/use-summary-failure-count.golden.xml",
40+
},
41+
}
42+
for _, tC := range testCases {
43+
t.Run(tC.desc, func(t *testing.T) {
44+
var output bytes.Buffer
45+
err := printJunit(&output, tC.summary)
46+
if err != nil {
47+
if len(tC.expectErr) == 0 {
48+
t.Fatalf("Expected nil error but got %v", err)
49+
}
50+
if err.Error() != tC.expectErr {
51+
t.Fatalf("Expected error %q but got %q", err, tC.expectErr)
52+
}
53+
}
54+
55+
if *update {
56+
ioutil.WriteFile(tC.expectFile, output.Bytes(), 0666)
57+
} else {
58+
fileData, err := ioutil.ReadFile(tC.expectFile)
59+
if err != nil {
60+
t.Fatalf("Failed to read golden file %v: %v", tC.expectFile, err)
61+
}
62+
if !bytes.Equal(fileData, output.Bytes()) {
63+
t.Errorf("Expected junit to equal goldenfile: %v but instead got:\n\n%v", tC.expectFile, output.String())
64+
}
65+
}
66+
})
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<testsuite tests="0" failures="0" time="" name="cyclonus"><properties></properties><testcase classname="" name="test1" time=""></testcase><testcase classname="" name="test2 with spaces" time=""><failure message="" type=""></failure></testcase><testcase classname="" name="test3 with + special %chars/" time=""></testcase><testcase classname="" name="test4 with&#xA;newlines" time=""><failure message="" type=""></failure></testcase></testsuite>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<testsuite tests="0" failures="0" time="" name="cyclonus"><properties></properties></testsuite>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<testsuite tests="0" failures="10" time="" name="cyclonus"><properties></properties></testsuite>

0 commit comments

Comments
 (0)