xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/bzltestutil/xml.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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