blob: 7af943b3e6224f21ada50debd99c9505efe874fb [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package xunit contains types and functions for manipulating xunit
// files.
package xunit
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"v.io/jiri"
"v.io/x/devtools/tooldata"
)
type TestSuites struct {
Suites []TestSuite `xml:"testsuite"`
XMLName xml.Name `xml:"testsuites"`
}
type TestSuite struct {
Name string `xml:"name,attr"`
Cases []TestCase `xml:"testcase"`
Errors int `xml:"errors,attr"`
Failures int `xml:"failures,attr"`
Skip int `xml:"skip,attr"`
Tests int `xml:"tests,attr"`
}
type TestCase struct {
Name string `xml:"name,attr"`
Classname string `xml:"classname,attr"`
Errors []Error `xml:"error"`
Failures []Failure `xml:"failure"`
Time string `xml:"time,attr"`
Skipped []string `xml:"skipped"`
}
type Error struct {
Message string `xml:"message,attr"`
Data string `xml:",chardata"`
}
type Failure struct {
Message string `xml:"message,attr"`
Data string `xml:",chardata"`
}
// CreateReport generates an xUnit report using the given test suites.
func CreateReport(jirix *jiri.X, testName string, suites []TestSuite) error {
result := TestSuites{Suites: suites}
bytes, err := xml.MarshalIndent(result, "", " ")
if err != nil {
return fmt.Errorf("MarshalIndent(%v) failed: %v", result, err)
}
if err := jirix.NewSeq().WriteFile(ReportPath(testName), bytes, os.FileMode(0644)).Done(); err != nil {
return fmt.Errorf("WriteFile(%v) failed: %v", ReportPath(testName), err)
}
return nil
}
// CreateTestSuiteWithFailure encodes the given information as a test
// suite with a single failure.
func CreateTestSuiteWithFailure(pkgName, testName, failureMessage, failureOutput string, duration time.Duration) *TestSuite {
s := TestSuite{Name: pkgName}
c := TestCase{
Classname: pkgName,
Name: testName,
Time: fmt.Sprintf("%.2f", duration.Seconds()),
}
s.Tests = 1
f := Failure{
Message: failureMessage,
Data: failureOutput,
}
c.Failures = append(c.Failures, f)
s.Failures = 1
s.Cases = append(s.Cases, c)
return &s
}
// CreateFailureReport creates an xUnit report for the given failure.
func CreateFailureReport(jirix *jiri.X, testName, pkgName, testCaseName, failureMessage, failureOutput string) error {
s := CreateTestSuiteWithFailure(pkgName, testCaseName, failureMessage, failureOutput, 0)
if err := CreateReport(jirix, testName, []TestSuite{*s}); err != nil {
return err
}
return nil
}
// ReportPath returns the path to the xUnit file.
//
// TODO(jsimsa): Once all Jenkins shell test scripts are ported to Go,
// change the filename to xunit_report_<testName>.xml.
func ReportPath(testName string) string {
workspace, fileName := os.Getenv("WORKSPACE"), fmt.Sprintf("tests_%s.xml", strings.Replace(testName, "-", "_", -1))
if workspace == "" {
return filepath.Join(os.Getenv("HOME"), "tmp", testName, fileName)
} else {
return filepath.Join(workspace, fileName)
}
}
// TestSuitesFromGoTestOutput reads data from the given input, assuming
// it contains test results generated by "go test -v", and returns it
// as an in-memory data structure.
func TestSuitesFromGoTestOutput(jirix *jiri.X, testOutput io.Reader) ([]*TestSuite, error) {
bin, err := tooldata.ThirdPartyBinPath(jirix, "go2xunit")
if err != nil {
return nil, err
}
var out bytes.Buffer
if err := jirix.NewSeq().Read(testOutput).Capture(&out, nil).Last(bin); err != nil {
return nil, err
}
var suite TestSuite
if err := xml.Unmarshal(out.Bytes(), &suite); err != nil {
return nil, fmt.Errorf("Unmarshal() failed: %v\n%v", err, out.String())
}
if suite.Tests > 0 {
return []*TestSuite{&suite}, nil
}
// go2xunit has most likely output multiple testsuites i.e.
// <testsuites>
// <testsuite>
// </testsuite>
// <testsuite>
// </testsuite>
// </testsuites>
// which results in zero tests if Unmarshal is called expecting a single
// testsuite. This seems to happen when a test itself invokes a test.
var suites TestSuites
if err := xml.Unmarshal(out.Bytes(), &suites); err != nil {
if !strings.Contains(err.Error(), "expected element type <testsuites> but have <testsuite>") {
return nil, fmt.Errorf("Unmarshal() failed: %v\n%v", err, out.String())
}
}
if len(suites.Suites) == 0 {
return []*TestSuite{&suite}, nil
}
rc := make([]*TestSuite, len(suites.Suites), len(suites.Suites))
for i, _ := range suites.Suites {
rc[i] = &suites.Suites[i]
}
return rc, nil
}