Skip to content

Commit 570cae3

Browse files
author
Ish Shah
authored
Add XUnit Formatting Output to Scorecard (#5048)
1 parent fb2a657 commit 570cae3

File tree

5 files changed

+136
-2
lines changed

5 files changed

+136
-2
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# entries is a list of entries to include in
2+
# release notes and/or the migration guide
3+
entries:
4+
- description: >
5+
Provide XML formatting option for scorecard users. Additionally transforms scorecard result types to xunit testsuite/testcase layout.
6+
7+
# kind is one of:
8+
# - addition
9+
# - change
10+
# - deprecation
11+
# - removal
12+
# - bugfix
13+
kind: "addition"
14+
15+
# Is this a breaking change?
16+
breaking: false

internal/cmd/operator-sdk/scorecard/cmd.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ package scorecard
1717
import (
1818
"context"
1919
"encoding/json"
20+
"encoding/xml"
2021
"errors"
2122
"fmt"
2223
"os"
2324
"path/filepath"
25+
"strings"
2426
"time"
2527

2628
"github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3"
@@ -30,6 +32,7 @@ import (
3032
"k8s.io/apimachinery/pkg/labels"
3133

3234
scorecardannotations "github.com/operator-framework/operator-sdk/internal/annotations/scorecard"
35+
xunit "github.com/operator-framework/operator-sdk/internal/cmd/operator-sdk/scorecard/xunit"
3336
"github.com/operator-framework/operator-sdk/internal/flags"
3437
registryutil "github.com/operator-framework/operator-sdk/internal/registry"
3538
"github.com/operator-framework/operator-sdk/internal/scorecard"
@@ -73,7 +76,7 @@ If the argument holds an image tag, it must be present remotely.`,
7376
scorecardCmd.Flags().StringVarP(&c.config, "config", "c", "", "path to scorecard config file")
7477
scorecardCmd.Flags().StringVarP(&c.namespace, "namespace", "n", "", "namespace to run the test images in")
7578
scorecardCmd.Flags().StringVarP(&c.outputFormat, "output", "o", "text",
76-
"Output format for results. Valid values: text, json")
79+
"Output format for results. Valid values: text, json, xunit")
7780
scorecardCmd.Flags().StringVarP(&c.serviceAccount, "service-account", "s", "default",
7881
"Service account to use for tests")
7982
scorecardCmd.Flags().BoolVarP(&c.list, "list", "L", false,
@@ -102,12 +105,49 @@ func (c *scorecardCmd) printOutput(output v1alpha3.TestList) error {
102105
return fmt.Errorf("marshal json error: %v", err)
103106
}
104107
fmt.Printf("%s\n", string(bytes))
108+
case "xunit":
109+
xunitOutput := c.convertXunit(output)
110+
bytes, err := xml.MarshalIndent(xunitOutput, "", " ")
111+
if err != nil {
112+
return fmt.Errorf("marshal xml error: %v", err)
113+
}
114+
fmt.Printf("%s\n", string(bytes))
105115
default:
106116
return fmt.Errorf("invalid output format selected")
107117
}
108118
return nil
109119
}
110120

121+
func (c *scorecardCmd) convertXunit(output v1alpha3.TestList) xunit.TestSuites {
122+
var resultSuite xunit.TestSuites
123+
resultSuite.Name = "scorecard"
124+
125+
jsonTestItems := output.Items
126+
for _, item := range jsonTestItems {
127+
tempResults := item.Status.Results
128+
for _, res := range tempResults {
129+
var tCase xunit.TestCase
130+
var tSuite xunit.TestSuite
131+
tSuite.Name = res.Name
132+
tCase.Name = res.Name
133+
if res.State == v1alpha3.ErrorState {
134+
tCase.Errors = append(tCase.Errors, xunit.XUnitComplexError{Type: "Error", Message: strings.Join(res.Errors, ",")})
135+
tSuite.Errors = strings.Join(res.Errors, ",")
136+
} else if res.State == v1alpha3.FailState {
137+
tCase.Failures = append(tCase.Failures, xunit.XUnitComplexFailure{Type: "Failure", Message: res.Log})
138+
tSuite.Failures = res.Log
139+
}
140+
tSuite.TestCases = append(tSuite.TestCases, tCase)
141+
tSuite.URL = item.Spec.Image
142+
//TODO: Add TestStuite ID when API updates version
143+
//tSuite.ID = item.Spec.UniqueID
144+
resultSuite.TestSuite = append(resultSuite.TestSuite, tSuite)
145+
}
146+
}
147+
148+
return resultSuite
149+
}
150+
111151
func (c *scorecardCmd) run() (err error) {
112152
// Extract bundle image contents if bundle is inferred to be an image.
113153
if _, err = os.Stat(c.bundle); err != nil && errors.Is(err, os.ErrNotExist) {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2020 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package xunitapi
16+
17+
// TestCase contain the core information from a test run, including its name and status
18+
type TestCase struct {
19+
// Name is the name of the test
20+
Name string `json:"name,omitempty"`
21+
Time string `json:"time,omitempty"`
22+
Classname string `json:"classname,omitempty"`
23+
Group string `json:"group,omitempty"`
24+
Failures []XUnitComplexFailure `json:"failure,omitempty"`
25+
Errors []XUnitComplexError `json:"error,omitempty"`
26+
Skipped []XUnitComplexSkipped `json:"skipped,omitempty"`
27+
}
28+
29+
// TestSuite contains for details about a test beyond the final status
30+
type TestSuite struct {
31+
// Name is the name of the test
32+
Name string `json:"name,omitempty"`
33+
Tests string `json:"tests,omitempty"`
34+
Failures string `json:"failures,omitempty"`
35+
Errors string `json:"errors,omitempty"`
36+
Group string `json:"group,omitempty"`
37+
Skipped string `json:"skipped,omitempty"`
38+
Timestamp string `json:"timestamp,omitempty"`
39+
Hostname string `json:"hostnames,omitempty"`
40+
ID string `json:"id,omitempty"`
41+
Package string `json:"package,omitempty"`
42+
File string `json:"file,omitempty"`
43+
Log string `json:"log,omitempty"`
44+
URL string `json:"url,omitempty"`
45+
Version string `json:"version,omitempty"`
46+
TestSuites []TestSuite `json:"testsuite,omitempty"`
47+
TestCases []TestCase `json:"testcase,omitempty"`
48+
}
49+
50+
// TestSuites is the top level object for amassing Xunit test results
51+
type TestSuites struct {
52+
// Name is the name of the test
53+
Name string `json:"name,omitempty"`
54+
Tests string `json:"tests,omitempty"`
55+
Failures string `json:"failures,omitempty"`
56+
Errors string `json:"errors,omitempty"`
57+
TestSuite []TestSuite `json:"testsuite,omitempty"`
58+
}
59+
60+
// XUnitComplexError contains a type header along with the error messages
61+
type XUnitComplexError struct {
62+
Type string `json:"type,omitempty"`
63+
Message string `json:"message,omitempty"`
64+
}
65+
66+
// XUnitComplexFailure contains a type header along with the failure logs
67+
type XUnitComplexFailure struct {
68+
Type string `json:"type,omitempty"`
69+
Message string `json:"message,omitempty"`
70+
}
71+
72+
// XUnitComplexSkipped contains a type header along with associated run logs
73+
type XUnitComplexSkipped struct {
74+
Type string `json:"type,omitempty"`
75+
Message string `json:"message,omitempty"`
76+
}

internal/scorecard/scorecard.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ func (o Scorecard) runTest(ctx context.Context, test v1alpha3.TestConfiguration)
132132
}
133133

134134
out := v1alpha3.NewTest()
135+
//TODO: Add timestamp to result when API version updates
136+
//out.Tstamp = time.Now().Format(time.RFC850)
135137
out.Spec = test
136138
out.Status = *result
137139
return out

website/content/en/docs/cli/operator-sdk_scorecard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ operator-sdk scorecard [flags]
2323
--kubeconfig string kubeconfig path
2424
-L, --list Option to enable listing which tests are run
2525
-n, --namespace string namespace to run the test images in
26-
-o, --output string Output format for results. Valid values: text, json (default "text")
26+
-o, --output string Output format for results. Valid values: text, json, xunit (default "text")
2727
-l, --selector string label selector to determine which tests are run
2828
-s, --service-account string Service account to use for tests (default "default")
2929
-x, --skip-cleanup Disable resource cleanup after tests are run

0 commit comments

Comments
 (0)