github.com/fafucoder/cilium@v1.6.11/test/ginkgo-ext/junit_reporter.go (about)

     1  package ginkgoext
     2  
     3  /*
     4  
     5  JUnit XML Reporter for Ginkgo
     6  
     7  For usage instructions: http://onsi.github.io/ginkgo/#generating_junit_xml_output
     8  
     9  Reference file:
    10  https://github.com/onsi/ginkgo/blob/39febac9157b63aeba74d843d6b1c30990f3d7ed/reporters/junit_reporter.go
    11  Junit Reference:
    12  https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_9.1.1/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html
    13  */
    14  
    15  import (
    16  	"encoding/xml"
    17  	"fmt"
    18  	"math"
    19  	"os"
    20  	"strings"
    21  
    22  	"github.com/onsi/ginkgo/config"
    23  	"github.com/onsi/ginkgo/types"
    24  )
    25  
    26  // JUnitTestSuite main struct to report all test in Junit Format
    27  type JUnitTestSuite struct {
    28  	XMLName   xml.Name        `xml:"testsuite"`
    29  	TestCases []JUnitTestCase `xml:"testcase"`
    30  	Name      string          `xml:"name,attr"`
    31  	Tests     int             `xml:"tests,attr"`
    32  	Failures  int             `xml:"failures,attr"`
    33  	Errors    int             `xml:"errors,attr"`
    34  	Time      float64         `xml:"time,attr"`
    35  }
    36  
    37  // JUnitTestCase test case struct to report in Junit Format.
    38  type JUnitTestCase struct {
    39  	Name           string               `xml:"name,attr"`
    40  	ClassName      string               `xml:"classname,attr"`
    41  	FailureMessage *JUnitFailureMessage `xml:"failure,omitempty"`
    42  	Skipped        *JUnitSkipped        `xml:"skipped,omitempty"`
    43  	Time           float64              `xml:"time,attr"`
    44  	SystemErr      string               `xml:"system-err,omitempty"`
    45  	SystemOut      string               `xml:"system-out,omitempty"`
    46  }
    47  
    48  // JUnitFailureMessage failure message struct
    49  type JUnitFailureMessage struct {
    50  	Type    string `xml:"type,attr"`
    51  	Message string `xml:",chardata"`
    52  }
    53  
    54  // JUnitSkipped skipped struct to report XML
    55  type JUnitSkipped struct {
    56  	XMLName xml.Name `xml:"skipped"`
    57  }
    58  
    59  // JUnitReporter struct that uses ginkgo to report
    60  type JUnitReporter struct {
    61  	suite         JUnitTestSuite
    62  	filename      string
    63  	testSuiteName string
    64  }
    65  
    66  //NewJUnitReporter creates a new JUnit XML reporter.  The XML will be stored in the passed in filename.
    67  func NewJUnitReporter(filename string) *JUnitReporter {
    68  	return &JUnitReporter{
    69  		filename: filename,
    70  	}
    71  }
    72  
    73  // SpecSuiteWillBegin create the main JUnitTestSuite based on Ginkgo parameters
    74  func (reporter *JUnitReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
    75  	reporter.suite = JUnitTestSuite{
    76  		Name:      summary.SuiteDescription,
    77  		TestCases: []JUnitTestCase{},
    78  	}
    79  	reporter.testSuiteName = summary.SuiteDescription
    80  }
    81  
    82  // SpecWillRun needed by ginkgo. Not used.
    83  func (reporter *JUnitReporter) SpecWillRun(specSummary *types.SpecSummary) {
    84  }
    85  
    86  // BeforeSuiteDidRun Beforesuite report function
    87  func (reporter *JUnitReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
    88  	reporter.handleSetupSummary("BeforeSuite", setupSummary)
    89  }
    90  
    91  // AfterSuiteDidRun ginkgo.Aftersuite report function
    92  func (reporter *JUnitReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
    93  	reporter.handleSetupSummary("AfterSuite", setupSummary)
    94  }
    95  
    96  func failureMessage(failure types.SpecFailure) string {
    97  	return fmt.Sprintf("%s\n%s\n%s", failure.ComponentCodeLocation.String(), failure.Message, failure.Location.String())
    98  }
    99  
   100  func (reporter *JUnitReporter) handleSetupSummary(name string, setupSummary *types.SetupSummary) {
   101  	if setupSummary.State != types.SpecStatePassed {
   102  		testCase := JUnitTestCase{
   103  			Name:      name,
   104  			ClassName: reporter.testSuiteName,
   105  		}
   106  
   107  		testCase.FailureMessage = &JUnitFailureMessage{
   108  			Type:    reporter.failureTypeForState(setupSummary.State),
   109  			Message: failureMessage(setupSummary.Failure),
   110  		}
   111  		testCase.SystemErr = setupSummary.CapturedOutput
   112  		testCase.Time = setupSummary.RunTime.Seconds()
   113  		reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
   114  	}
   115  }
   116  
   117  // SpecDidComplete reports the test results in a JUTestCase struct
   118  func (reporter *JUnitReporter) SpecDidComplete(specSummary *types.SpecSummary) {
   119  	testCase := JUnitTestCase{
   120  		Name:      strings.Join(specSummary.ComponentTexts[1:], " "),
   121  		ClassName: reporter.testSuiteName,
   122  	}
   123  	if specSummary.State == types.SpecStateFailed || specSummary.State == types.SpecStateTimedOut || specSummary.State == types.SpecStatePanicked {
   124  		testCase.FailureMessage = &JUnitFailureMessage{
   125  			Type:    reporter.failureTypeForState(specSummary.State),
   126  			Message: failureMessage(specSummary.Failure),
   127  		}
   128  		checks, output := reportChecks(specSummary.CapturedOutput)
   129  		testCase.SystemErr = checks
   130  		testCase.SystemOut = output
   131  	}
   132  	if specSummary.State == types.SpecStateSkipped || specSummary.State == types.SpecStatePending {
   133  		testCase.Skipped = &JUnitSkipped{}
   134  	}
   135  	testCase.Time = specSummary.RunTime.Seconds()
   136  	reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
   137  }
   138  
   139  // SpecSuiteDidEnd summary information to Suite summary in the xml report
   140  func (reporter *JUnitReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
   141  	reporter.suite.Tests = summary.NumberOfSpecsThatWillBeRun
   142  	reporter.suite.Time = math.Trunc(summary.RunTime.Seconds() * 1000 / 1000)
   143  	reporter.suite.Failures = summary.NumberOfFailedSpecs
   144  	reporter.suite.Errors = 0
   145  	file, err := os.Create(reporter.filename)
   146  	if err != nil {
   147  		fmt.Printf("Failed to create JUnit report file: %s\n\t%s", reporter.filename, err.Error())
   148  	}
   149  	defer file.Close()
   150  	file.WriteString(xml.Header)
   151  	encoder := xml.NewEncoder(file)
   152  	encoder.Indent("  ", "    ")
   153  	err = encoder.Encode(reporter.suite)
   154  	if err != nil {
   155  		fmt.Printf("Failed to generate JUnit report\n\t%s", err.Error())
   156  	}
   157  }
   158  
   159  func (reporter *JUnitReporter) failureTypeForState(state types.SpecState) string {
   160  	switch state {
   161  	case types.SpecStateFailed:
   162  		return "Failure"
   163  	case types.SpecStateTimedOut:
   164  		return "Timeout"
   165  	case types.SpecStatePanicked:
   166  		return "Panic"
   167  	default:
   168  		return "Unknown"
   169  	}
   170  }
   171  
   172  // reportChecks filters the given string from the stodout and substract the
   173  // information that is within the <Checks></Checks> labels. It'll return the
   174  // given output as first result, and the check output in the second string.
   175  func reportChecks(output string) (string, string) {
   176  	var checks string
   177  	var stdout string
   178  	var dest = "stdout"
   179  
   180  	for _, line := range strings.Split(output, "\n") {
   181  		if line == "<Checks>" {
   182  			dest = "checks"
   183  			continue
   184  		}
   185  
   186  		if line == "</Checks>" {
   187  			dest = "stdout"
   188  			continue
   189  		}
   190  		switch dest {
   191  		case "stdout":
   192  			stdout += line + "\n"
   193  			continue
   194  		case "checks":
   195  			checks += line + "\n"
   196  			continue
   197  		}
   198  	}
   199  	return stdout, checks
   200  }