blob: a4239215c9b837f634958f0f39a688ca6d5f0284 [file] [log] [blame]
package unittest
import (
"encoding/xml"
"fmt"
"io"
"runtime"
"time"
)
// XUnitValidationMethod the default name for Helm XUnit validation.
const XUnitValidationMethod string = "Helm-Validation"
// XUnitAssemblies the top level of the document.
type XUnitAssemblies struct {
XMLName xml.Name `xml:"assemblies"`
Assembly []XUnitAssembly `xml:"assembly,omitempty"`
}
// XUnitAssembly is a run of a single test assembly.
type XUnitAssembly struct {
XMLName xml.Name `xml:"assembly"`
Name string `xml:"name,attr"`
ConfigFile string `xml:"config-file,attr"`
TestFramework string `xml:"test-framework,attr"`
Environment string `xml:"environment"`
RunDate string `xml:"run-date,attr"`
RunTime string `xml:"run-time,attr"`
Time string `xml:"time,attr"`
TotalTests int `xml:"total,attr"`
PassedTests int `xml:"passed,attr"`
FailedTests int `xml:"failed,attr"`
SkippedTests int `xml:"skipped,attr"`
ErrorsTests int `xml:"errors,attr"`
Errors []XUnitError `xml:"errors>error,omitempty"`
TestRuns []XUnitTestRun `xml:"collection,omitempty"`
}
// XUnitTestRun is a single XUnit test suite which may contain many
// testcases.
type XUnitTestRun struct {
XMLName xml.Name `xml:"collection"`
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
TotalTests int `xml:"total,attr"`
PassedTests int `xml:"passed,attr"`
FailedTests int `xml:"failed,attr"`
SkippedTests int `xml:"skipped,attr"`
TestCases []XUnitTestCase `xml:"test"`
}
// XUnitErrors is a testsuitecategory
type XUnitErrors struct {
XMLName xml.Name `xml:"errors"`
Errors []XUnitError `xml:"error,omitempty"`
}
// XUnitError contains error information.
type XUnitError struct {
Type string `xml:"type,attr"`
Name string `xml:"name,attr"`
Failure *XUnitFailure `xml:"failure"`
}
// XUnitFailure contains data related to a failed test.
type XUnitFailure struct {
XMLName xml.Name `xml:"failure"`
ExceptionType string `xml:"exception-type,attr"`
Message *XUnitFailureMessage `xml:"message,omitempty"`
StackTrace *XUnitFailureStackTrace `xml:"stack-trace,omitempty"`
}
// XUnitFailureMessage contains the message of the failed test.
type XUnitFailureMessage struct {
XMLName xml.Name `xml:"message"`
Data string `xml:",cdata"`
}
// XUnitFailureStackTrace constains the stacktrace of the failed test.
type XUnitFailureStackTrace struct {
XMLName xml.Name `xml:"stack-trace"`
Data string `xml:",cdata"`
}
// XUnitTestCase is a single test case with its result.
type XUnitTestCase struct {
XMLName xml.Name `xml:"test"`
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Method string `xml:"method,attr"`
Time string `xml:"time,attr"`
Result string `xml:"result,attr"`
Traits []XUnitTrait `xml:"traits>trait,omitempty"`
Failure *XUnitFailure `xml:"failure,omitempty"`
Reason *XUnitReason `xml:"reason,omitempty"`
}
// XUnitReason contains reason why a test is skipped.
type XUnitReason struct {
XMLName xml.Name `xml:"reason"`
Reason string `xml:",cdata"`
}
// XUnitTrait contains a name/value pair.
type XUnitTrait struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
type xUnitReportXML struct{}
// NewXUnitReportXML Constructor
func NewXUnitReportXML() Formatter {
return &xUnitReportXML{}
}
// XUnitReportXML writes a NUnit xml representation of the given report to w
// in the format described at https://github.com/nunit/docs/wiki/XML-Formats
func (x *xUnitReportXML) WriteTestOutput(testSuiteResults []*TestSuiteResult, noXMLHeader bool, w io.Writer) error {
currentTime := time.Now()
testAssemblies := []XUnitAssembly{}
// convert TestSuiteResults to NUnit test suites
for _, testSuiteResult := range testSuiteResults {
ts := x.createXUnitAssembly(currentTime, testSuiteResult)
// When ExecError found, direct create error and
// add to the list and iterater trough next testSuiteResult.
if testSuiteResult.ExecError != nil {
ts.TotalTests++
ts.ErrorsTests++
ts.Errors = []XUnitError{
x.createXUnitError(
"Error",
"Error",
x.createXUnitFailure(
fmt.Sprintf("%s-%s", XUnitValidationMethod, "Error"),
"Error",
testSuiteResult.ExecError.Error(),
),
),
}
testAssemblies = append(testAssemblies, ts)
continue
}
ts.TestRuns = []XUnitTestRun{
x.createXUnitTestRun(testSuiteResult),
}
// individual test cases
for _, test := range testSuiteResult.TestsResult {
ts.TotalTests++
ts.TestRuns[0].TotalTests++
testCase := x.createXUnitTestCase(determineClassnameFromDisplayName(testSuiteResult.DisplayName), test)
// Write when a test is failed
if !test.Passed {
testCase.Failure = x.createXUnitFailure(XUnitValidationMethod, "Failed", test.stringify())
// Update error count and ExceptionType
if test.ExecError != nil {
ts.ErrorsTests++
testCase.Failure.ExceptionType = fmt.Sprintf("%s-%s", XUnitValidationMethod, "Error")
} else {
ts.FailedTests++
ts.TestRuns[0].FailedTests++
}
} else {
ts.PassedTests++
ts.TestRuns[0].PassedTests++
}
ts.TestRuns[0].TestCases = append(ts.TestRuns[0].TestCases, testCase)
}
testAssemblies = append(testAssemblies, ts)
}
xunitResult := XUnitAssemblies{
Assembly: testAssemblies,
}
// to xml
if err := writeContentToFile(noXMLHeader, xunitResult, w); err != nil {
return err
}
return nil
}
func (x *xUnitReportXML) formatResult(b bool) string {
if !b {
return "Fail"
}
return "Pass"
}
func (x *xUnitReportXML) createXUnitAssembly(currentTime time.Time, testSuiteResult *TestSuiteResult) XUnitAssembly {
return XUnitAssembly{
Name: testSuiteResult.FilePath,
ConfigFile: testSuiteResult.FilePath,
TestFramework: testFramework,
Environment: fmt.Sprintf("%s.%s-%s", runtime.Version(), runtime.GOOS, runtime.GOARCH),
RunDate: formatDate(currentTime),
RunTime: formatTime(currentTime),
Time: formatDuration(testSuiteResult.calculateTestSuiteDuration()),
TotalTests: 0,
PassedTests: 0,
FailedTests: 0,
SkippedTests: 0,
ErrorsTests: 0,
}
}
func (x *xUnitReportXML) createXUnitTestRun(testSuiteResult *TestSuiteResult) XUnitTestRun {
return XUnitTestRun{
Name: testSuiteResult.DisplayName,
Time: formatDuration(testSuiteResult.calculateTestSuiteDuration()),
TotalTests: 0,
PassedTests: 0,
FailedTests: 0,
SkippedTests: 0,
TestCases: []XUnitTestCase{},
}
}
func (x *xUnitReportXML) createXUnitTestCase(className string, testJobResult *TestJobResult) XUnitTestCase {
return XUnitTestCase{
Name: testJobResult.DisplayName,
Type: className,
Method: XUnitValidationMethod,
Time: formatDuration(testJobResult.Duration),
Result: x.formatResult(testJobResult.Passed),
Failure: nil,
}
}
func (x *xUnitReportXML) createXUnitFailure(exceptionType, failureMessage, stackTrace string) *XUnitFailure {
return &XUnitFailure{
ExceptionType: exceptionType,
Message: &XUnitFailureMessage{
Data: failureMessage,
},
StackTrace: &XUnitFailureStackTrace{
Data: stackTrace,
},
}
}
func (x *xUnitReportXML) createXUnitError(errorType, errorName string, xunitFailure *XUnitFailure) XUnitError {
return XUnitError{
Type: errorType,
Name: errorName,
Failure: xunitFailure,
}
}