github.com/data-DOG/godog@v0.7.9/fmt_junit.go (about)

     1  package godog
     2  
     3  import (
     4  	"encoding/xml"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/DATA-DOG/godog/gherkin"
    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{
    19  		suite: &junitPackageSuite{
    20  			Name:       suite,
    21  			TestSuites: make([]*junitTestSuite, 0),
    22  		},
    23  		out:     out,
    24  		started: timeNowFunc(),
    25  	}
    26  }
    27  
    28  type junitFormatter struct {
    29  	suite *junitPackageSuite
    30  	out   io.Writer
    31  
    32  	// timing
    33  	started     time.Time
    34  	caseStarted time.Time
    35  	featStarted time.Time
    36  
    37  	outline        *gherkin.ScenarioOutline
    38  	outlineExample int
    39  }
    40  
    41  func (j *junitFormatter) Feature(feature *gherkin.Feature, path string, c []byte) {
    42  	testSuite := &junitTestSuite{
    43  		TestCases: make([]*junitTestCase, 0),
    44  		Name:      feature.Name,
    45  	}
    46  
    47  	if len(j.suite.TestSuites) > 0 {
    48  		j.current().Time = timeNowFunc().Sub(j.featStarted).String()
    49  	}
    50  	j.featStarted = timeNowFunc()
    51  	j.suite.TestSuites = append(j.suite.TestSuites, testSuite)
    52  }
    53  
    54  func (j *junitFormatter) Defined(*gherkin.Step, *StepDef) {
    55  
    56  }
    57  
    58  func (j *junitFormatter) Node(node interface{}) {
    59  	suite := j.current()
    60  	tcase := &junitTestCase{}
    61  
    62  	switch t := node.(type) {
    63  	case *gherkin.ScenarioOutline:
    64  		j.outline = t
    65  		j.outlineExample = 0
    66  		return
    67  	case *gherkin.Scenario:
    68  		tcase.Name = t.Name
    69  		suite.Tests++
    70  		j.suite.Tests++
    71  	case *gherkin.TableRow:
    72  		j.outlineExample++
    73  		tcase.Name = fmt.Sprintf("%s #%d", j.outline.Name, j.outlineExample)
    74  		suite.Tests++
    75  		j.suite.Tests++
    76  	default:
    77  		return
    78  	}
    79  	j.caseStarted = timeNowFunc()
    80  	suite.TestCases = append(suite.TestCases, tcase)
    81  }
    82  
    83  func (j *junitFormatter) Failed(step *gherkin.Step, match *StepDef, err error) {
    84  	suite := j.current()
    85  	suite.Failures++
    86  	j.suite.Failures++
    87  
    88  	tcase := suite.current()
    89  	tcase.Time = timeNowFunc().Sub(j.caseStarted).String()
    90  	tcase.Status = "failed"
    91  	tcase.Failure = &junitFailure{
    92  		Message: fmt.Sprintf("%s %s: %s", step.Type, step.Text, err.Error()),
    93  	}
    94  }
    95  
    96  func (j *junitFormatter) Passed(step *gherkin.Step, match *StepDef) {
    97  	suite := j.current()
    98  
    99  	tcase := suite.current()
   100  	tcase.Time = timeNowFunc().Sub(j.caseStarted).String()
   101  	tcase.Status = "passed"
   102  }
   103  
   104  func (j *junitFormatter) Skipped(step *gherkin.Step, match *StepDef) {
   105  	suite := j.current()
   106  
   107  	tcase := suite.current()
   108  	tcase.Time = timeNowFunc().Sub(j.caseStarted).String()
   109  	tcase.Error = append(tcase.Error, &junitError{
   110  		Type:    "skipped",
   111  		Message: fmt.Sprintf("%s %s", step.Type, step.Text),
   112  	})
   113  }
   114  
   115  func (j *junitFormatter) Undefined(step *gherkin.Step, match *StepDef) {
   116  	suite := j.current()
   117  	tcase := suite.current()
   118  	if tcase.Status != "undefined" {
   119  		// do not count two undefined steps as another error
   120  		suite.Errors++
   121  		j.suite.Errors++
   122  	}
   123  
   124  	tcase.Time = timeNowFunc().Sub(j.caseStarted).String()
   125  	tcase.Status = "undefined"
   126  	tcase.Error = append(tcase.Error, &junitError{
   127  		Type:    "undefined",
   128  		Message: fmt.Sprintf("%s %s", step.Type, step.Text),
   129  	})
   130  }
   131  
   132  func (j *junitFormatter) Pending(step *gherkin.Step, match *StepDef) {
   133  	suite := j.current()
   134  	suite.Errors++
   135  	j.suite.Errors++
   136  
   137  	tcase := suite.current()
   138  	tcase.Time = timeNowFunc().Sub(j.caseStarted).String()
   139  	tcase.Status = "pending"
   140  	tcase.Error = append(tcase.Error, &junitError{
   141  		Type:    "pending",
   142  		Message: fmt.Sprintf("%s %s: TODO: write pending definition", step.Type, step.Text),
   143  	})
   144  }
   145  
   146  func (j *junitFormatter) Summary() {
   147  	if j.current() != nil {
   148  		j.current().Time = timeNowFunc().Sub(j.featStarted).String()
   149  	}
   150  	j.suite.Time = timeNowFunc().Sub(j.started).String()
   151  	_, err := io.WriteString(j.out, xml.Header)
   152  	if err != nil {
   153  		fmt.Fprintln(os.Stderr, "failed to write junit string:", err)
   154  	}
   155  	enc := xml.NewEncoder(j.out)
   156  	enc.Indent("", s(2))
   157  	if err = enc.Encode(j.suite); err != nil {
   158  		fmt.Fprintln(os.Stderr, "failed to write junit xml:", err)
   159  	}
   160  }
   161  
   162  type junitFailure struct {
   163  	Message string `xml:"message,attr"`
   164  	Type    string `xml:"type,attr,omitempty"`
   165  }
   166  
   167  type junitError struct {
   168  	XMLName xml.Name `xml:"error,omitempty"`
   169  	Message string   `xml:"message,attr"`
   170  	Type    string   `xml:"type,attr"`
   171  }
   172  
   173  type junitTestCase struct {
   174  	XMLName xml.Name      `xml:"testcase"`
   175  	Name    string        `xml:"name,attr"`
   176  	Status  string        `xml:"status,attr"`
   177  	Time    string        `xml:"time,attr"`
   178  	Failure *junitFailure `xml:"failure,omitempty"`
   179  	Error   []*junitError
   180  }
   181  
   182  type junitTestSuite struct {
   183  	XMLName   xml.Name `xml:"testsuite"`
   184  	Name      string   `xml:"name,attr"`
   185  	Tests     int      `xml:"tests,attr"`
   186  	Skipped   int      `xml:"skipped,attr"`
   187  	Failures  int      `xml:"failures,attr"`
   188  	Errors    int      `xml:"errors,attr"`
   189  	Time      string   `xml:"time,attr"`
   190  	TestCases []*junitTestCase
   191  }
   192  
   193  func (ts *junitTestSuite) current() *junitTestCase {
   194  	return ts.TestCases[len(ts.TestCases)-1]
   195  }
   196  
   197  type junitPackageSuite struct {
   198  	XMLName    xml.Name `xml:"testsuites"`
   199  	Name       string   `xml:"name,attr"`
   200  	Tests      int      `xml:"tests,attr"`
   201  	Skipped    int      `xml:"skipped,attr"`
   202  	Failures   int      `xml:"failures,attr"`
   203  	Errors     int      `xml:"errors,attr"`
   204  	Time       string   `xml:"time,attr"`
   205  	TestSuites []*junitTestSuite
   206  }
   207  
   208  func (j *junitFormatter) current() *junitTestSuite {
   209  	return j.suite.TestSuites[len(j.suite.TestSuites)-1]
   210  }