Files
kubernetes/vendor/gotest.tools/gotestsum/internal/junitxml/report.go
2019-07-31 17:43:02 -04:00

174 lines
4.5 KiB
Go

/*Package junitxml creates a JUnit XML report from a testjson.Execution.
*/
package junitxml
import (
"encoding/xml"
"fmt"
"io"
"os"
"os/exec"
"strings"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"gotest.tools/gotestsum/testjson"
)
// JUnitTestSuites is a collection of JUnit test suites.
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite
}
// JUnitTestSuite is a single JUnit test suite which may contain many
// testcases.
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
}
// JUnitTestCase is a single test case with its result.
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"`
}
// JUnitSkipMessage contains the reason why a testcase was skipped.
type JUnitSkipMessage struct {
Message string `xml:"message,attr"`
}
// JUnitProperty represents a key/value pair used to define properties.
type JUnitProperty struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
// JUnitFailure contains data related to a failed test.
type JUnitFailure struct {
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Contents string `xml:",chardata"`
}
// Write creates an XML document and writes it to out.
func Write(out io.Writer, exec *testjson.Execution) error {
return errors.Wrap(write(out, generate(exec)), "failed to write JUnit XML")
}
func generate(exec *testjson.Execution) JUnitTestSuites {
version := goVersion()
suites := JUnitTestSuites{}
for _, pkgname := range exec.Packages() {
pkg := exec.Package(pkgname)
junitpkg := JUnitTestSuite{
Name: pkgname,
Tests: pkg.Total,
Time: formatDurationAsSeconds(pkg.Elapsed()),
Properties: packageProperties(version),
TestCases: packageTestCases(pkg),
Failures: len(pkg.Failed),
}
suites.Suites = append(suites.Suites, junitpkg)
}
return suites
}
func formatDurationAsSeconds(d time.Duration) string {
return fmt.Sprintf("%f", d.Seconds())
}
func packageProperties(goVersion string) []JUnitProperty {
return []JUnitProperty{
{Name: "go.version", Value: goVersion},
}
}
// goVersion returns the version as reported by the go binary in PATH. This
// version will not be the same as runtime.Version, which is always the version
// of go used to build the gotestsum binary.
//
// To skip the os/exec call set the GOVERSION environment variable to the
// desired value.
func goVersion() string {
if version, ok := os.LookupEnv("GOVERSION"); ok {
return version
}
logrus.Debugf("exec: go version")
cmd := exec.Command("go", "version")
out, err := cmd.Output()
if err != nil {
logrus.WithError(err).Warn("failed to lookup go version for junit xml")
return "unknown"
}
return strings.TrimPrefix(strings.TrimSpace(string(out)), "go version ")
}
func packageTestCases(pkg *testjson.Package) []JUnitTestCase {
cases := []JUnitTestCase{}
if pkg.TestMainFailed() {
jtc := newJUnitTestCase(testjson.TestCase{
Test: "TestMain",
})
jtc.Failure = &JUnitFailure{
Message: "Failed",
Contents: pkg.Output(""),
}
cases = append(cases, jtc)
}
for _, tc := range pkg.Failed {
jtc := newJUnitTestCase(tc)
jtc.Failure = &JUnitFailure{
Message: "Failed",
Contents: pkg.Output(tc.Test),
}
cases = append(cases, jtc)
}
for _, tc := range pkg.Skipped {
jtc := newJUnitTestCase(tc)
jtc.SkipMessage = &JUnitSkipMessage{Message: pkg.Output(tc.Test)}
cases = append(cases, jtc)
}
for _, tc := range pkg.Passed {
jtc := newJUnitTestCase(tc)
cases = append(cases, jtc)
}
return cases
}
func newJUnitTestCase(tc testjson.TestCase) JUnitTestCase {
return JUnitTestCase{
Classname: tc.Package,
Name: tc.Test,
Time: formatDurationAsSeconds(tc.Elapsed),
}
}
func write(out io.Writer, suites JUnitTestSuites) error {
doc, err := xml.MarshalIndent(suites, "", "\t")
if err != nil {
return err
}
_, err = out.Write([]byte(xml.Header))
if err != nil {
return err
}
_, err = out.Write(doc)
return err
}