1*9bb1b549SSpandan Das// Copyright 2020 The Bazel Authors. All rights reserved. 2*9bb1b549SSpandan Das// 3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License"); 4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License. 5*9bb1b549SSpandan Das// You may obtain a copy of the License at 6*9bb1b549SSpandan Das// 7*9bb1b549SSpandan Das// http://www.apache.org/licenses/LICENSE-2.0 8*9bb1b549SSpandan Das// 9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software 10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS, 11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and 13*9bb1b549SSpandan Das// limitations under the License. 14*9bb1b549SSpandan Das 15*9bb1b549SSpandan Daspackage bzltestutil 16*9bb1b549SSpandan Das 17*9bb1b549SSpandan Dasimport ( 18*9bb1b549SSpandan Das "encoding/json" 19*9bb1b549SSpandan Das "encoding/xml" 20*9bb1b549SSpandan Das "fmt" 21*9bb1b549SSpandan Das "io" 22*9bb1b549SSpandan Das "path" 23*9bb1b549SSpandan Das "sort" 24*9bb1b549SSpandan Das "strings" 25*9bb1b549SSpandan Das "time" 26*9bb1b549SSpandan Das) 27*9bb1b549SSpandan Das 28*9bb1b549SSpandan Dastype xmlTestSuites struct { 29*9bb1b549SSpandan Das XMLName xml.Name `xml:"testsuites"` 30*9bb1b549SSpandan Das Suites []xmlTestSuite `xml:"testsuite"` 31*9bb1b549SSpandan Das} 32*9bb1b549SSpandan Das 33*9bb1b549SSpandan Dastype xmlTestSuite struct { 34*9bb1b549SSpandan Das XMLName xml.Name `xml:"testsuite"` 35*9bb1b549SSpandan Das TestCases []xmlTestCase `xml:"testcase"` 36*9bb1b549SSpandan Das Errors int `xml:"errors,attr"` 37*9bb1b549SSpandan Das Failures int `xml:"failures,attr"` 38*9bb1b549SSpandan Das Skipped int `xml:"skipped,attr"` 39*9bb1b549SSpandan Das Tests int `xml:"tests,attr"` 40*9bb1b549SSpandan Das Time string `xml:"time,attr"` 41*9bb1b549SSpandan Das Name string `xml:"name,attr"` 42*9bb1b549SSpandan Das} 43*9bb1b549SSpandan Das 44*9bb1b549SSpandan Dastype xmlTestCase struct { 45*9bb1b549SSpandan Das XMLName xml.Name `xml:"testcase"` 46*9bb1b549SSpandan Das Classname string `xml:"classname,attr"` 47*9bb1b549SSpandan Das Name string `xml:"name,attr"` 48*9bb1b549SSpandan Das Time string `xml:"time,attr"` 49*9bb1b549SSpandan Das Failure *xmlMessage `xml:"failure,omitempty"` 50*9bb1b549SSpandan Das Error *xmlMessage `xml:"error,omitempty"` 51*9bb1b549SSpandan Das Skipped *xmlMessage `xml:"skipped,omitempty"` 52*9bb1b549SSpandan Das} 53*9bb1b549SSpandan Das 54*9bb1b549SSpandan Dastype xmlMessage struct { 55*9bb1b549SSpandan Das Message string `xml:"message,attr"` 56*9bb1b549SSpandan Das Type string `xml:"type,attr"` 57*9bb1b549SSpandan Das Contents string `xml:",chardata"` 58*9bb1b549SSpandan Das} 59*9bb1b549SSpandan Das 60*9bb1b549SSpandan Das// jsonEvent as encoded by the test2json package. 61*9bb1b549SSpandan Dastype jsonEvent struct { 62*9bb1b549SSpandan Das Time *time.Time 63*9bb1b549SSpandan Das Action string 64*9bb1b549SSpandan Das Package string 65*9bb1b549SSpandan Das Test string 66*9bb1b549SSpandan Das Elapsed *float64 67*9bb1b549SSpandan Das Output string 68*9bb1b549SSpandan Das} 69*9bb1b549SSpandan Das 70*9bb1b549SSpandan Dastype testCase struct { 71*9bb1b549SSpandan Das state string 72*9bb1b549SSpandan Das output strings.Builder 73*9bb1b549SSpandan Das duration *float64 74*9bb1b549SSpandan Das} 75*9bb1b549SSpandan Das 76*9bb1b549SSpandan Das// json2xml converts test2json's output into an xml output readable by Bazel. 77*9bb1b549SSpandan Das// http://windyroad.com.au/dl/Open%20Source/JUnit.xsd 78*9bb1b549SSpandan Dasfunc json2xml(r io.Reader, pkgName string) ([]byte, error) { 79*9bb1b549SSpandan Das var pkgDuration *float64 80*9bb1b549SSpandan Das testcases := make(map[string]*testCase) 81*9bb1b549SSpandan Das testCaseByName := func(name string) *testCase { 82*9bb1b549SSpandan Das if name == "" { 83*9bb1b549SSpandan Das return nil 84*9bb1b549SSpandan Das } 85*9bb1b549SSpandan Das if _, ok := testcases[name]; !ok { 86*9bb1b549SSpandan Das testcases[name] = &testCase{} 87*9bb1b549SSpandan Das } 88*9bb1b549SSpandan Das return testcases[name] 89*9bb1b549SSpandan Das } 90*9bb1b549SSpandan Das 91*9bb1b549SSpandan Das dec := json.NewDecoder(r) 92*9bb1b549SSpandan Das for { 93*9bb1b549SSpandan Das var e jsonEvent 94*9bb1b549SSpandan Das if err := dec.Decode(&e); err == io.EOF { 95*9bb1b549SSpandan Das break 96*9bb1b549SSpandan Das } else if err != nil { 97*9bb1b549SSpandan Das return nil, fmt.Errorf("error decoding test2json output: %s", err) 98*9bb1b549SSpandan Das } 99*9bb1b549SSpandan Das switch s := e.Action; s { 100*9bb1b549SSpandan Das case "run": 101*9bb1b549SSpandan Das if c := testCaseByName(e.Test); c != nil { 102*9bb1b549SSpandan Das c.state = s 103*9bb1b549SSpandan Das } 104*9bb1b549SSpandan Das case "output": 105*9bb1b549SSpandan Das if c := testCaseByName(e.Test); c != nil { 106*9bb1b549SSpandan Das c.output.WriteString(e.Output) 107*9bb1b549SSpandan Das } 108*9bb1b549SSpandan Das case "skip": 109*9bb1b549SSpandan Das if c := testCaseByName(e.Test); c != nil { 110*9bb1b549SSpandan Das c.output.WriteString(e.Output) 111*9bb1b549SSpandan Das c.state = s 112*9bb1b549SSpandan Das c.duration = e.Elapsed 113*9bb1b549SSpandan Das } 114*9bb1b549SSpandan Das case "fail": 115*9bb1b549SSpandan Das if c := testCaseByName(e.Test); c != nil { 116*9bb1b549SSpandan Das c.state = s 117*9bb1b549SSpandan Das c.duration = e.Elapsed 118*9bb1b549SSpandan Das } else { 119*9bb1b549SSpandan Das pkgDuration = e.Elapsed 120*9bb1b549SSpandan Das } 121*9bb1b549SSpandan Das case "pass": 122*9bb1b549SSpandan Das if c := testCaseByName(e.Test); c != nil { 123*9bb1b549SSpandan Das c.duration = e.Elapsed 124*9bb1b549SSpandan Das c.state = s 125*9bb1b549SSpandan Das } else { 126*9bb1b549SSpandan Das pkgDuration = e.Elapsed 127*9bb1b549SSpandan Das } 128*9bb1b549SSpandan Das } 129*9bb1b549SSpandan Das } 130*9bb1b549SSpandan Das 131*9bb1b549SSpandan Das return xml.MarshalIndent(toXML(pkgName, pkgDuration, testcases), "", "\t") 132*9bb1b549SSpandan Das} 133*9bb1b549SSpandan Das 134*9bb1b549SSpandan Dasfunc toXML(pkgName string, pkgDuration *float64, testcases map[string]*testCase) *xmlTestSuites { 135*9bb1b549SSpandan Das cases := make([]string, 0, len(testcases)) 136*9bb1b549SSpandan Das for k := range testcases { 137*9bb1b549SSpandan Das cases = append(cases, k) 138*9bb1b549SSpandan Das } 139*9bb1b549SSpandan Das sort.Strings(cases) 140*9bb1b549SSpandan Das suite := xmlTestSuite{ 141*9bb1b549SSpandan Das Name: pkgName, 142*9bb1b549SSpandan Das } 143*9bb1b549SSpandan Das if pkgDuration != nil { 144*9bb1b549SSpandan Das suite.Time = fmt.Sprintf("%.3f", *pkgDuration) 145*9bb1b549SSpandan Das } 146*9bb1b549SSpandan Das for _, name := range cases { 147*9bb1b549SSpandan Das c := testcases[name] 148*9bb1b549SSpandan Das suite.Tests++ 149*9bb1b549SSpandan Das newCase := xmlTestCase{ 150*9bb1b549SSpandan Das Name: name, 151*9bb1b549SSpandan Das Classname: path.Base(pkgName), 152*9bb1b549SSpandan Das } 153*9bb1b549SSpandan Das if c.duration != nil { 154*9bb1b549SSpandan Das newCase.Time = fmt.Sprintf("%.3f", *c.duration) 155*9bb1b549SSpandan Das } 156*9bb1b549SSpandan Das switch c.state { 157*9bb1b549SSpandan Das case "skip": 158*9bb1b549SSpandan Das suite.Skipped++ 159*9bb1b549SSpandan Das newCase.Skipped = &xmlMessage{ 160*9bb1b549SSpandan Das Message: "Skipped", 161*9bb1b549SSpandan Das Contents: c.output.String(), 162*9bb1b549SSpandan Das } 163*9bb1b549SSpandan Das case "fail": 164*9bb1b549SSpandan Das suite.Failures++ 165*9bb1b549SSpandan Das newCase.Failure = &xmlMessage{ 166*9bb1b549SSpandan Das Message: "Failed", 167*9bb1b549SSpandan Das Contents: c.output.String(), 168*9bb1b549SSpandan Das } 169*9bb1b549SSpandan Das case "pass": 170*9bb1b549SSpandan Das break 171*9bb1b549SSpandan Das default: 172*9bb1b549SSpandan Das suite.Errors++ 173*9bb1b549SSpandan Das newCase.Error = &xmlMessage{ 174*9bb1b549SSpandan Das Message: "No pass/skip/fail event found for test", 175*9bb1b549SSpandan Das Contents: c.output.String(), 176*9bb1b549SSpandan Das } 177*9bb1b549SSpandan Das } 178*9bb1b549SSpandan Das suite.TestCases = append(suite.TestCases, newCase) 179*9bb1b549SSpandan Das } 180*9bb1b549SSpandan Das return &xmlTestSuites{Suites: []xmlTestSuite{suite}} 181*9bb1b549SSpandan Das} 182