github.com/lonnblad/godog@v0.7.14-0.20200306004719-1b0cb3259847/fmt_junit.go (about)

     1  package godog
     2  
     3  import (
     4  	"encoding/xml"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"sort"
     9  	"strconv"
    10  	"time"
    11  )
    12  
    13  func init() {
    14  	Format("junit", "Prints junit compatible xml to stdout", junitFunc)
    15  }
    16  
    17  func junitFunc(suite string, out io.Writer) Formatter {
    18  	return &junitFormatter{basefmt: newBaseFmt(suite, out)}
    19  }
    20  
    21  type junitFormatter struct {
    22  	*basefmt
    23  }
    24  
    25  func (f *junitFormatter) Summary() {
    26  	suite := buildJUNITPackageSuite(f.suiteName, f.started, f.features)
    27  
    28  	_, err := io.WriteString(f.out, xml.Header)
    29  	if err != nil {
    30  		fmt.Fprintln(os.Stderr, "failed to write junit string:", err)
    31  	}
    32  
    33  	enc := xml.NewEncoder(f.out)
    34  	enc.Indent("", s(2))
    35  	if err = enc.Encode(suite); err != nil {
    36  		fmt.Fprintln(os.Stderr, "failed to write junit xml:", err)
    37  	}
    38  }
    39  
    40  func (f *junitFormatter) Sync(cf ConcurrentFormatter) {
    41  	if source, ok := cf.(*junitFormatter); ok {
    42  		f.basefmt.Sync(source.basefmt)
    43  	}
    44  }
    45  
    46  func (f *junitFormatter) Copy(cf ConcurrentFormatter) {
    47  	if source, ok := cf.(*junitFormatter); ok {
    48  		f.basefmt.Copy(source.basefmt)
    49  	}
    50  }
    51  
    52  func junitTimeDuration(from, to time.Time) string {
    53  	return strconv.FormatFloat(to.Sub(from).Seconds(), 'f', -1, 64)
    54  }
    55  
    56  func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*feature) junitPackageSuite {
    57  	suite := junitPackageSuite{
    58  		Name:       suiteName,
    59  		TestSuites: make([]*junitTestSuite, len(features)),
    60  		Time:       junitTimeDuration(startedAt, timeNowFunc()),
    61  	}
    62  
    63  	sort.Sort(sortByName(features))
    64  
    65  	for idx, feat := range features {
    66  		ts := junitTestSuite{
    67  			Name:      feat.GherkinDocument.Feature.Name,
    68  			Time:      junitTimeDuration(feat.startedAt(), feat.finishedAt()),
    69  			TestCases: make([]*junitTestCase, len(feat.pickleResults)),
    70  		}
    71  
    72  		var testcaseNames = make(map[string]int)
    73  		for _, pickleResult := range feat.pickleResults {
    74  			testcaseNames[pickleResult.Name] = testcaseNames[pickleResult.Name] + 1
    75  		}
    76  
    77  		var outlineNo = make(map[string]int)
    78  		for idx, pickleResult := range feat.pickleResults {
    79  			tc := junitTestCase{}
    80  			tc.Time = junitTimeDuration(pickleResult.startedAt(), pickleResult.finishedAt())
    81  
    82  			tc.Name = pickleResult.Name
    83  			if testcaseNames[tc.Name] > 1 {
    84  				outlineNo[tc.Name] = outlineNo[tc.Name] + 1
    85  				tc.Name += fmt.Sprintf(" #%d", outlineNo[tc.Name])
    86  			}
    87  
    88  			ts.Tests++
    89  			suite.Tests++
    90  
    91  			for _, stepResult := range pickleResult.stepResults {
    92  				switch stepResult.status {
    93  				case passed:
    94  					tc.Status = passed.String()
    95  				case failed:
    96  					tc.Status = failed.String()
    97  					tc.Failure = &junitFailure{
    98  						Message: fmt.Sprintf("Step %s: %s", stepResult.step.Text, stepResult.err),
    99  					}
   100  				case skipped:
   101  					tc.Error = append(tc.Error, &junitError{
   102  						Type:    "skipped",
   103  						Message: fmt.Sprintf("Step %s", stepResult.step.Text),
   104  					})
   105  				case undefined:
   106  					tc.Status = undefined.String()
   107  					tc.Error = append(tc.Error, &junitError{
   108  						Type:    "undefined",
   109  						Message: fmt.Sprintf("Step %s", stepResult.step.Text),
   110  					})
   111  				case pending:
   112  					tc.Status = pending.String()
   113  					tc.Error = append(tc.Error, &junitError{
   114  						Type:    "pending",
   115  						Message: fmt.Sprintf("Step %s: TODO: write pending definition", stepResult.step.Text),
   116  					})
   117  				}
   118  			}
   119  
   120  			switch tc.Status {
   121  			case failed.String():
   122  				ts.Failures++
   123  				suite.Failures++
   124  			case undefined.String(), pending.String():
   125  				ts.Errors++
   126  				suite.Errors++
   127  			}
   128  
   129  			ts.TestCases[idx] = &tc
   130  		}
   131  
   132  		suite.TestSuites[idx] = &ts
   133  	}
   134  
   135  	return suite
   136  }
   137  
   138  type junitFailure struct {
   139  	Message string `xml:"message,attr"`
   140  	Type    string `xml:"type,attr,omitempty"`
   141  }
   142  
   143  type junitError struct {
   144  	XMLName xml.Name `xml:"error,omitempty"`
   145  	Message string   `xml:"message,attr"`
   146  	Type    string   `xml:"type,attr"`
   147  }
   148  
   149  type junitTestCase struct {
   150  	XMLName xml.Name      `xml:"testcase"`
   151  	Name    string        `xml:"name,attr"`
   152  	Status  string        `xml:"status,attr"`
   153  	Time    string        `xml:"time,attr"`
   154  	Failure *junitFailure `xml:"failure,omitempty"`
   155  	Error   []*junitError
   156  }
   157  
   158  type junitTestSuite struct {
   159  	XMLName   xml.Name `xml:"testsuite"`
   160  	Name      string   `xml:"name,attr"`
   161  	Tests     int      `xml:"tests,attr"`
   162  	Skipped   int      `xml:"skipped,attr"`
   163  	Failures  int      `xml:"failures,attr"`
   164  	Errors    int      `xml:"errors,attr"`
   165  	Time      string   `xml:"time,attr"`
   166  	TestCases []*junitTestCase
   167  }
   168  
   169  type junitPackageSuite struct {
   170  	XMLName    xml.Name `xml:"testsuites"`
   171  	Name       string   `xml:"name,attr"`
   172  	Tests      int      `xml:"tests,attr"`
   173  	Skipped    int      `xml:"skipped,attr"`
   174  	Failures   int      `xml:"failures,attr"`
   175  	Errors     int      `xml:"errors,attr"`
   176  	Time       string   `xml:"time,attr"`
   177  	TestSuites []*junitTestSuite
   178  }