aboutsummaryrefslogtreecommitdiff
path: root/go/tools/bzltestutil/xml.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/tools/bzltestutil/xml.go')
-rw-r--r--go/tools/bzltestutil/xml.go181
1 files changed, 181 insertions, 0 deletions
diff --git a/go/tools/bzltestutil/xml.go b/go/tools/bzltestutil/xml.go
new file mode 100644
index 00000000..d8ecd1d9
--- /dev/null
+++ b/go/tools/bzltestutil/xml.go
@@ -0,0 +1,181 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bzltestutil
+
+import (
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "io"
+ "path"
+ "sort"
+ "strings"
+ "time"
+)
+
+type xmlTestSuites struct {
+ XMLName xml.Name `xml:"testsuites"`
+ Suites []xmlTestSuite `xml:"testsuite"`
+}
+
+type xmlTestSuite struct {
+ XMLName xml.Name `xml:"testsuite"`
+ TestCases []xmlTestCase `xml:"testcase"`
+ Errors int `xml:"errors,attr"`
+ Failures int `xml:"failures,attr"`
+ Skipped int `xml:"skipped,attr"`
+ Tests int `xml:"tests,attr"`
+ Time string `xml:"time,attr"`
+ Name string `xml:"name,attr"`
+}
+
+type xmlTestCase struct {
+ XMLName xml.Name `xml:"testcase"`
+ Classname string `xml:"classname,attr"`
+ Name string `xml:"name,attr"`
+ Time string `xml:"time,attr"`
+ Failure *xmlMessage `xml:"failure,omitempty"`
+ Error *xmlMessage `xml:"error,omitempty"`
+ Skipped *xmlMessage `xml:"skipped,omitempty"`
+}
+
+type xmlMessage struct {
+ Message string `xml:"message,attr"`
+ Type string `xml:"type,attr"`
+ Contents string `xml:",chardata"`
+}
+
+// jsonEvent as encoded by the test2json package.
+type jsonEvent struct {
+ Time *time.Time
+ Action string
+ Package string
+ Test string
+ Elapsed *float64
+ Output string
+}
+
+type testCase struct {
+ state string
+ output strings.Builder
+ duration *float64
+}
+
+// json2xml converts test2json's output into an xml output readable by Bazel.
+// http://windyroad.com.au/dl/Open%20Source/JUnit.xsd
+func json2xml(r io.Reader, pkgName string) ([]byte, error) {
+ var pkgDuration *float64
+ testcases := make(map[string]*testCase)
+ testCaseByName := func(name string) *testCase {
+ if name == "" {
+ return nil
+ }
+ if _, ok := testcases[name]; !ok {
+ testcases[name] = &testCase{}
+ }
+ return testcases[name]
+ }
+
+ dec := json.NewDecoder(r)
+ for {
+ var e jsonEvent
+ if err := dec.Decode(&e); err == io.EOF {
+ break
+ } else if err != nil {
+ return nil, fmt.Errorf("error decoding test2json output: %s", err)
+ }
+ switch s := e.Action; s {
+ case "run":
+ if c := testCaseByName(e.Test); c != nil {
+ c.state = s
+ }
+ case "output":
+ if c := testCaseByName(e.Test); c != nil {
+ c.output.WriteString(e.Output)
+ }
+ case "skip":
+ if c := testCaseByName(e.Test); c != nil {
+ c.output.WriteString(e.Output)
+ c.state = s
+ c.duration = e.Elapsed
+ }
+ case "fail":
+ if c := testCaseByName(e.Test); c != nil {
+ c.state = s
+ c.duration = e.Elapsed
+ } else {
+ pkgDuration = e.Elapsed
+ }
+ case "pass":
+ if c := testCaseByName(e.Test); c != nil {
+ c.duration = e.Elapsed
+ c.state = s
+ } else {
+ pkgDuration = e.Elapsed
+ }
+ }
+ }
+
+ return xml.MarshalIndent(toXML(pkgName, pkgDuration, testcases), "", "\t")
+}
+
+func toXML(pkgName string, pkgDuration *float64, testcases map[string]*testCase) *xmlTestSuites {
+ cases := make([]string, 0, len(testcases))
+ for k := range testcases {
+ cases = append(cases, k)
+ }
+ sort.Strings(cases)
+ suite := xmlTestSuite{
+ Name: pkgName,
+ }
+ if pkgDuration != nil {
+ suite.Time = fmt.Sprintf("%.3f", *pkgDuration)
+ }
+ for _, name := range cases {
+ c := testcases[name]
+ suite.Tests++
+ newCase := xmlTestCase{
+ Name: name,
+ Classname: path.Base(pkgName),
+ }
+ if c.duration != nil {
+ newCase.Time = fmt.Sprintf("%.3f", *c.duration)
+ }
+ switch c.state {
+ case "skip":
+ suite.Skipped++
+ newCase.Skipped = &xmlMessage{
+ Message: "Skipped",
+ Contents: c.output.String(),
+ }
+ case "fail":
+ suite.Failures++
+ newCase.Failure = &xmlMessage{
+ Message: "Failed",
+ Contents: c.output.String(),
+ }
+ case "pass":
+ break
+ default:
+ suite.Errors++
+ newCase.Error = &xmlMessage{
+ Message: "No pass/skip/fail event found for test",
+ Contents: c.output.String(),
+ }
+ }
+ suite.TestCases = append(suite.TestCases, newCase)
+ }
+ return &xmlTestSuites{Suites: []xmlTestSuite{suite}}
+}