mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-11 14:09:21 +00:00
185 lines
4.6 KiB
Go
185 lines
4.6 KiB
Go
package output
|
|
|
|
// References:
|
|
// https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd
|
|
// https://llg.cubic.org/docs/junit/
|
|
// https://github.com/jstemmer/go-junit-report/blob/master/formatter/formatter.go
|
|
// https://github.com/junit-team/junit5/blob/main/platform-tests/src/test/resources/jenkins-junit.xsd
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/yannh/kubeconform/pkg/validator"
|
|
)
|
|
|
|
type TestSuiteCollection struct {
|
|
XMLName xml.Name `xml:"testsuites"`
|
|
Name string `xml:"name,attr"`
|
|
Time float64 `xml:"time,attr"`
|
|
Tests int `xml:"tests,attr"`
|
|
Failures int `xml:"failures,attr"`
|
|
Disabled int `xml:"disabled,attr"`
|
|
Errors int `xml:"errors,attr"`
|
|
Suites []TestSuite `xml:"testsuite"`
|
|
}
|
|
|
|
type Property struct {
|
|
Name string `xml:"name,attr"`
|
|
Value string `xml:"value,attr"`
|
|
}
|
|
|
|
type TestSuite struct {
|
|
XMLName xml.Name `xml:"testsuite"`
|
|
Cases []TestCase `xml:"testcase"`
|
|
Name string `xml:"name,attr"`
|
|
Id int `xml:"id,attr"`
|
|
Tests int `xml:"tests,attr"`
|
|
Failures int `xml:"failures,attr"`
|
|
Errors int `xml:"errors,attr"`
|
|
Disabled int `xml:"disabled,attr"`
|
|
Skipped int `xml:"skipped,attr"`
|
|
}
|
|
|
|
type TestCase struct {
|
|
XMLName xml.Name `xml:"testcase"`
|
|
Name string `xml:"name,attr"`
|
|
ClassName string `xml:"classname,attr"`
|
|
Time int `xml:"time,attr"` // Optional, but for Buildkite support https://github.com/yannh/kubeconform/issues/127
|
|
Skipped *TestCaseSkipped `xml:"skipped,omitempty"`
|
|
Error *TestCaseError `xml:"error,omitempty"`
|
|
Failure []TestCaseError `xml:"failure,omitempty"`
|
|
}
|
|
|
|
type TestCaseSkipped struct {
|
|
Message string `xml:"message,attr"`
|
|
}
|
|
|
|
type TestCaseError struct {
|
|
Message string `xml:"message,attr"`
|
|
Type string `xml:"type,attr"`
|
|
Content string `xml:",chardata"`
|
|
}
|
|
|
|
type junito struct {
|
|
id int
|
|
w io.Writer
|
|
withSummary bool
|
|
verbose bool
|
|
suitesIndex map[string]int // map filename to index in suites
|
|
suites []TestSuite
|
|
startTime time.Time
|
|
}
|
|
|
|
func junitOutput(w io.Writer, withSummary bool, isStdin, verbose bool) Output {
|
|
return &junito{
|
|
id: 0,
|
|
w: w,
|
|
withSummary: withSummary,
|
|
verbose: verbose,
|
|
suites: []TestSuite{},
|
|
suitesIndex: make(map[string]int),
|
|
startTime: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Write adds a result to the report.
|
|
func (o *junito) Write(result validator.Result) error {
|
|
var suite TestSuite
|
|
i, found := o.suitesIndex[result.Resource.Path]
|
|
|
|
if !found {
|
|
o.id++
|
|
suite = TestSuite{
|
|
Name: result.Resource.Path,
|
|
Id: o.id,
|
|
Tests: 0, Failures: 0, Errors: 0, Disabled: 0, Skipped: 0,
|
|
Cases: make([]TestCase, 0),
|
|
}
|
|
o.suites = append(o.suites, suite)
|
|
i = len(o.suites) - 1
|
|
o.suitesIndex[result.Resource.Path] = i
|
|
}
|
|
|
|
sig, _ := result.Resource.Signature()
|
|
var objectName string
|
|
if len(sig.Namespace) > 0 {
|
|
objectName = fmt.Sprintf("%s/%s", sig.Namespace, sig.Name)
|
|
} else {
|
|
objectName = sig.Name
|
|
}
|
|
typeName := fmt.Sprintf("%s@%s", sig.Kind, sig.Version)
|
|
testCase := TestCase{ClassName: typeName, Name: objectName}
|
|
|
|
switch result.Status {
|
|
case validator.Valid:
|
|
case validator.Invalid:
|
|
o.suites[i].Failures++
|
|
failure := TestCaseError{Message: result.Err.Error()}
|
|
testCase.Failure = append(testCase.Failure, failure)
|
|
case validator.Error:
|
|
o.suites[i].Errors++
|
|
testCase.Error = &TestCaseError{Message: result.Err.Error()}
|
|
case validator.Skipped:
|
|
testCase.Skipped = &TestCaseSkipped{}
|
|
o.suites[i].Skipped++
|
|
case validator.Empty:
|
|
return nil
|
|
}
|
|
|
|
o.suites[i].Tests++
|
|
o.suites[i].Cases = append(o.suites[i].Cases, testCase)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Flush outputs the results as XML
|
|
func (o *junito) Flush() error {
|
|
runtime := time.Now().Sub(o.startTime)
|
|
|
|
totalValid := 0
|
|
totalInvalid := 0
|
|
totalErrors := 0
|
|
totalSkipped := 0
|
|
|
|
for _, suite := range o.suites {
|
|
for _, tCase := range suite.Cases {
|
|
if tCase.Error != nil {
|
|
totalErrors++
|
|
} else if tCase.Skipped != nil {
|
|
totalSkipped++
|
|
} else if len(tCase.Failure) > 0 {
|
|
totalInvalid++
|
|
} else {
|
|
totalValid++
|
|
}
|
|
}
|
|
}
|
|
|
|
root := TestSuiteCollection{
|
|
Name: "kubeconform",
|
|
Time: runtime.Seconds(),
|
|
Tests: totalValid + totalInvalid + totalErrors + totalSkipped,
|
|
Failures: totalInvalid,
|
|
Errors: totalErrors,
|
|
Disabled: totalSkipped,
|
|
Suites: o.suites,
|
|
}
|
|
|
|
// 2-space indentation
|
|
content, err := xml.MarshalIndent(root, "", " ")
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
writer := bufio.NewWriter(o.w)
|
|
writer.Write(content)
|
|
writer.WriteByte('\n')
|
|
writer.Flush()
|
|
|
|
return nil
|
|
}
|