diff options
Diffstat (limited to 'go/tools/bzltestutil/xml.go')
-rw-r--r-- | go/tools/bzltestutil/xml.go | 181 |
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}} +} |