...

Source file src/github.com/bazelbuild/rules_go/go/tools/bzltestutil/xml.go

Documentation: github.com/bazelbuild/rules_go/go/tools/bzltestutil

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

View as plain text