diff --git a/certification/formatters/formatters.go b/certification/formatters/formatters.go index 29ba0970..9610dcb0 100644 --- a/certification/formatters/formatters.go +++ b/certification/formatters/formatters.go @@ -86,6 +86,7 @@ func (f *genericFormatter) FileExtension() string { // availableFormatters maps configuration-friendly values to pretty representations // of the same value, and their corresponding Formatter included with this library. var availableFormatters = map[string]ResponseFormatter{ - "json": &genericFormatter{"Generic JSON", "json", genericJSONFormatter}, - "xml": &genericFormatter{"Generic XML", "xml", genericXMLFormatter}, + "json": &genericFormatter{"Generic JSON", "json", genericJSONFormatter}, + "xml": &genericFormatter{"Generic XML", "xml", genericXMLFormatter}, + "junitxml": &genericFormatter{"JUnit XML", "xml", junitXMLFormatter}, } diff --git a/certification/formatters/junitxml.go b/certification/formatters/junitxml.go new file mode 100644 index 00000000..c5ae5987 --- /dev/null +++ b/certification/formatters/junitxml.go @@ -0,0 +1,108 @@ +package formatters + +import ( + "encoding/xml" + "fmt" + "time" + + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/errors" + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" +) + +type JUnitTestSuites struct { + XMLName xml.Name `xml:"testsuites"` + Suites []JUnitTestSuite `xml:"testsuite"` +} + +type JUnitTestSuite struct { + XMLName xml.Name `xml:"testsuite"` + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Time string `xml:"time,attr"` + Name string `xml:"name,attr"` + Properties []JUnitProperty `xml:"properties>property,omitempty"` + TestCases []JUnitTestCase `xml:"testcase"` +} + +type JUnitTestCase struct { + XMLName xml.Name `xml:"testcase"` + Classname string `xml:"classname,attr"` + Name string `xml:"name,attr"` + Time string `xml:"time,attr"` + SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` + Failure *JUnitFailure `xml:"failure,omitempty"` + SystemOut string `xml:"system-out,omitempty"` + Message string `xml:",chardata"` +} + +type JUnitSkipMessage struct { + Message string `xml:"message,attr"` +} + +type JUnitProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type JUnitFailure struct { + Message string `xml:"message,attr"` + Type string `xml:"type,attr"` + Contents string `xml:",chardata"` +} + +func junitXMLFormatter(r runtime.Results) ([]byte, error) { + response := getResponse(r) + suites := JUnitTestSuites{} + testsuite := JUnitTestSuite{ + Tests: len(r.Errors) + len(r.Failed) + len(r.Passed), + Failures: len(r.Errors) + len(r.Failed), + Time: "0s", + Name: "Red Hat Certification", + Properties: []JUnitProperty{}, + TestCases: []JUnitTestCase{}, + } + + totalDuration := time.Duration(0) + for _, result := range r.Passed { + testCase := JUnitTestCase{ + Classname: response.Image, + Name: result.Name(), + Time: result.ElapsedTime.String(), + Failure: nil, + Message: result.Metadata().Description, + } + testsuite.TestCases = append(testsuite.TestCases, testCase) + totalDuration += result.ElapsedTime + } + + for _, result := range append(r.Errors, r.Failed...) { + testCase := JUnitTestCase{ + Classname: response.Image, + Name: result.Name(), + Time: result.ElapsedTime.String(), + Failure: &JUnitFailure{ + Message: "Failed", + Type: "", + Contents: fmt.Sprintf("%s: Suggested Fix: %s", result.Help().Message, result.Help().Suggestion), + }, + } + testsuite.TestCases = append(testsuite.TestCases, testCase) + totalDuration += result.ElapsedTime + } + + testsuite.Time = totalDuration.String() + suites.Suites = append(suites.Suites, testsuite) + + bytes, err := xml.MarshalIndent(suites, "", "\t") + if err != nil { + o := fmt.Errorf("%w with formatter %s: %s", + errors.ErrFormattingResults, + "junitxml", + err, + ) + + return nil, o + } + + return bytes, nil +} diff --git a/cmd/check.go b/cmd/check.go index ca314167..3c22b397 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -1,7 +1,12 @@ package cmd import ( + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/artifacts" + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/formatters" + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var checkCmd = &cobra.Command{ @@ -13,3 +18,29 @@ var checkCmd = &cobra.Command{ func init() { rootCmd.AddCommand(checkCmd) } + +func writeJunitIfEnabled(results runtime.Results) error { + if !viper.GetBool("junit") { + return nil + } + + var cfg runtime.Config + cfg.ResponseFormat = "junitxml" + + junitformatter, err := formatters.NewForConfig(cfg) + if err != nil { + return err + } + junitResults, err := junitformatter.Format(results) + if err != nil { + return err + } + + junitFilename, err := artifacts.WriteFile("results-junit.xml", string(junitResults)) + if err != nil { + return err + } + log.Tracef("JUnitXML written to %s", junitFilename) + + return nil +} diff --git a/cmd/check_container.go b/cmd/check_container.go index 20cb8a10..131c9f1f 100644 --- a/cmd/check_container.go +++ b/cmd/check_container.go @@ -84,6 +84,10 @@ var checkContainerCmd = &cobra.Command{ return err } + if err := writeJunitIfEnabled(results); err != nil { + return err + } + return nil }, } diff --git a/cmd/check_operator.go b/cmd/check_operator.go index 11b68e02..b2e4972c 100644 --- a/cmd/check_operator.go +++ b/cmd/check_operator.go @@ -89,6 +89,10 @@ var checkOperatorCmd = &cobra.Command{ return err } + if err := writeJunitIfEnabled(results); err != nil { + return err + } + return nil }, }