diff --git a/fmt_junit.go b/fmt_junit.go new file mode 100644 index 0000000..624d027 --- /dev/null +++ b/fmt_junit.go @@ -0,0 +1,139 @@ +package godog + +import ( + "encoding/xml" + "fmt" + "io" + "os" + "time" + + "github.com/adrianduke/godog/junit" + gherkin "github.com/cucumber/gherkin-go" +) + +const JUnitResultsEnv = "JUNIT_RESULTS" + +func init() { + Format("junit", "Prints out in junit compatible xml format", &junitFormatter{ + JUnit: make(junit.JUnit, 0), + }) +} + +type junitFormatter struct { + junit.JUnit +} + +func (j *junitFormatter) Feature(feature *gherkin.Feature, path string) { + testSuites := &junit.TestSuites{ + Name: feature.Name, + TestSuites: make([]*junit.TestSuite, 0), + } + + j.JUnit = append(j.JUnit, testSuites) +} + +func (j *junitFormatter) Node(node interface{}) { + testSuite := &junit.TestSuite{ + TestCases: make([]*junit.TestCase, 0), + Timestamp: time.Now(), + } + + switch t := node.(type) { + case *gherkin.ScenarioOutline: + testSuite.Name = t.Name + case *gherkin.Scenario: + testSuite.Name = t.Name + case *gherkin.Background: + testSuite.Name = "Background" + } + + currentSuites := j.JUnit.CurrentSuites() + currentSuites.TestSuites = append(currentSuites.TestSuites, testSuite) +} + +func (j *junitFormatter) Failed(step *gherkin.Step, match *StepDef, err error) { + testCase := &junit.TestCase{ + Name: step.Text, + } + + testCase.Failure = &junit.Failure{ + Contents: err.Error(), + } + + currentSuites := j.JUnit.CurrentSuites() + currentSuites.Failures++ + currentSuite := currentSuites.CurrentSuite() + currentSuite.Failures++ + currentSuite.TestCases = append(currentSuite.TestCases, testCase) +} + +func (j *junitFormatter) Passed(step *gherkin.Step, match *StepDef) { + testCase := &junit.TestCase{ + Name: step.Text, + } + + currentSuites := j.JUnit.CurrentSuites() + currentSuites.Tests++ + currentSuite := currentSuites.CurrentSuite() + currentSuite.Tests++ + currentSuite.TestCases = append(currentSuite.TestCases, testCase) +} + +func (j *junitFormatter) Skipped(step *gherkin.Step) { + testCase := &junit.TestCase{ + Name: step.Text, + } + + currentSuites := j.JUnit.CurrentSuites() + currentSuite := currentSuites.CurrentSuite() + currentSuite.Skipped++ + currentSuite.TestCases = append(currentSuite.TestCases, testCase) +} + +func (j *junitFormatter) Undefined(step *gherkin.Step) { + testCase := &junit.TestCase{ + Name: step.Text, + } + + currentSuites := j.JUnit.CurrentSuites() + currentSuites.Disabled++ + currentSuite := currentSuites.CurrentSuite() + currentSuite.Disabled++ + currentSuite.TestCases = append(currentSuite.TestCases, testCase) +} + +func (j *junitFormatter) Pending(step *gherkin.Step, match *StepDef) { + testCase := &junit.TestCase{ + Name: step.Text, + } + + testCase.Skipped = junit.Skipped{ + Contents: step.Text, + } + + currentSuites := j.JUnit.CurrentSuites() + currentSuite := currentSuites.CurrentSuite() + currentSuite.Skipped++ + currentSuite.TestCases = append(currentSuite.TestCases, testCase) +} + +func (j *junitFormatter) Summary() { + var writer io.Writer + if outputFilePath := os.Getenv(JUnitResultsEnv); outputFilePath != "" { + outputFile, err := os.Create(outputFilePath) + if err != nil { + panic(err.Error()) + } + defer outputFile.Close() + + writer = io.Writer(outputFile) + } else { + writer = os.Stdout + } + + enc := xml.NewEncoder(writer) + enc.Indent(" ", " ") + if err := enc.Encode(j.JUnit); err != nil { + fmt.Printf("error: %v\n", err) + } +} diff --git a/junit/juint-4.xsd b/junit/juint-4.xsd new file mode 100644 index 0000000..242d5b2 --- /dev/null +++ b/junit/juint-4.xsd @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/junit/structs.go b/junit/structs.go new file mode 100644 index 0000000..630efba --- /dev/null +++ b/junit/structs.go @@ -0,0 +1,93 @@ +package junit + +import ( + "encoding/xml" + "time" +) + +type Failure struct { + Message string `xml:"message,attr"` + Type string `xml:"type,attr"` + Contents string `xml:",chardata"` +} + +type Error struct { + Message string `xml:"message,attr"` + Type string `xml:"type,attr"` + Contents string `xml:",chardata"` +} + +type Property struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type Skipped struct { + Contents string `xml:",chardata"` +} + +type SystemErr struct { + Contents string `xml:",chardata"` +} + +type SystemOut struct { + Contents string `xml:",chardata"` +} + +type TestCase struct { + XMLName xml.Name `xml:"testcase"` + Name string `xml:"name,attr"` + Classname string `xml:"classname,attr"` + Assertions string `xml:"assertions,attr"` + Status string `xml:"status,attr"` + Time string `xml:"time,attr"` + Skipped *Skipped `xml:"skipped,omitempty"` + Failure *Failure `xml:"failure,omitempty"` + Error *Error `xml:"error,omitempty"` + SystemOut *SystemOut `xml:"system-out,omitempty"` + SystemErr *SystemErr `xml:"system-err,omitempty"` +} + +type TestSuite struct { + XMLName xml.Name `xml:"testsuite"` + Name string `xml:"name,attr"` + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Errors int `xml:"errors,attr"` + Disabled int `xml:"disabled,attr"` + Skipped int `xml:"skipped,attr"` + Time string `xml:"time,attr"` + Hostname string `xml:"hostname,attr"` + ID string `xml:"id,attr"` + Package string `xml:"package,attr"` + Timestamp time.Time `xml:"timestamp,attr"` + SystemOut *SystemOut `xml:"system-out,omitempty"` + SystemErr *SystemErr `xml:"system-err,omitempty"` + Properties []*Property `xml:"properties>property,omitempty"` + TestCases []*TestCase +} + +func (ts *TestSuite) CurrentCase() *TestCase { + return ts.TestCases[len(ts.TestCases)-1] +} + +type TestSuites struct { + XMLName xml.Name `xml:"testsuites"` + Name string `xml:"name,attr"` + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Errors int `xml:"errors,attr"` + Disabled int `xml:"disabled,attr"` + Time string `xml:"time,attr"` + TestSuites []*TestSuite +} + +func (ts *TestSuites) CurrentSuite() *TestSuite { + return ts.TestSuites[len(ts.TestSuites)-1] +} + +type JUnit []*TestSuites + +func (j JUnit) CurrentSuites() *TestSuites { + return j[len(j)-1] +}