| // 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 |
| } |