diff --git a/features/formatter/cucumber.feature b/features/formatter/cucumber.feature index c6c6ff1..229f8e2 100644 --- a/features/formatter/cucumber.feature +++ b/features/formatter/cucumber.feature @@ -261,7 +261,7 @@ Feature: cucumber json formatter }, "result": { "status": "passed", - "duration": -1 + "duration": 0 } }, { @@ -274,7 +274,7 @@ Feature: cucumber json formatter "result": { "status": "failed", "error_message": "intentional failure", - "duration": -1 + "duration": 0 } } ] @@ -329,7 +329,7 @@ Feature: cucumber json formatter }, "result": { "status": "passed", - "duration": -1 + "duration": 0 } } ] @@ -352,7 +352,7 @@ Feature: cucumber json formatter "result": { "status": "failed", "error_message": "intentional failure", - "duration": -1 + "duration": 0 } } ] @@ -452,7 +452,7 @@ Feature: cucumber json formatter }, "result": { "status": "passed", - "duration": -1 + "duration": 0 } } ] @@ -505,7 +505,7 @@ Feature: cucumber json formatter }, "result": { "status": "passed", - "duration": -1 + "duration": 0 } }, { diff --git a/fmt_junit.go b/fmt_junit.go index 6eafb18..de488f6 100644 --- a/fmt_junit.go +++ b/fmt_junit.go @@ -62,14 +62,12 @@ func (j *junitFormatter) Node(node interface{}) { switch t := node.(type) { case *gherkin.ScenarioOutline: j.outline = t + j.outlineExample = 0 return case *gherkin.Scenario: tcase.Name = t.Name suite.Tests++ j.suite.Tests++ - case *gherkin.Examples: - j.outlineExample = 0 - return case *gherkin.TableRow: j.outlineExample++ tcase.Name = fmt.Sprintf("%s #%d", j.outline.Name, j.outlineExample) @@ -78,9 +76,6 @@ func (j *junitFormatter) Node(node interface{}) { default: return } - if len(suite.TestCases) > 0 { - suite.current().Time = timeNowFunc().Sub(j.caseStarted).String() - } j.caseStarted = timeNowFunc() suite.TestCases = append(suite.TestCases, tcase) } @@ -91,6 +86,7 @@ func (j *junitFormatter) Failed(step *gherkin.Step, match *StepDef, err error) { j.suite.Failures++ tcase := suite.current() + tcase.Time = timeNowFunc().Sub(j.caseStarted).String() tcase.Status = "failed" tcase.Failure = &junitFailure{ Message: fmt.Sprintf("%s %s: %s", step.Type, step.Text, err.Error()), @@ -101,18 +97,31 @@ func (j *junitFormatter) Passed(step *gherkin.Step, match *StepDef) { suite := j.current() tcase := suite.current() + tcase.Time = timeNowFunc().Sub(j.caseStarted).String() tcase.Status = "passed" } func (j *junitFormatter) Skipped(step *gherkin.Step) { + suite := j.current() + + tcase := suite.current() + tcase.Time = timeNowFunc().Sub(j.caseStarted).String() + tcase.Error = append(tcase.Error, &junitError{ + Type: "skipped", + Message: fmt.Sprintf("%s %s", step.Type, step.Text), + }) } func (j *junitFormatter) Undefined(step *gherkin.Step) { suite := j.current() - suite.Errors++ - j.suite.Errors++ - tcase := suite.current() + if tcase.Status != "undefined" { + // do not count two undefined steps as another error + suite.Errors++ + j.suite.Errors++ + } + + tcase.Time = timeNowFunc().Sub(j.caseStarted).String() tcase.Status = "undefined" tcase.Error = append(tcase.Error, &junitError{ Type: "undefined", @@ -126,6 +135,7 @@ func (j *junitFormatter) Pending(step *gherkin.Step, match *StepDef) { j.suite.Errors++ tcase := suite.current() + tcase.Time = timeNowFunc().Sub(j.caseStarted).String() tcase.Status = "pending" tcase.Error = append(tcase.Error, &junitError{ Type: "pending", @@ -134,6 +144,9 @@ func (j *junitFormatter) Pending(step *gherkin.Step, match *StepDef) { } func (j *junitFormatter) Summary() { + if j.current() != nil { + j.current().Time = timeNowFunc().Sub(j.featStarted).String() + } j.suite.Time = timeNowFunc().Sub(j.started).String() io.WriteString(j.out, xml.Header) diff --git a/fmt_junit_test.go b/fmt_junit_test.go new file mode 100644 index 0000000..e701eca --- /dev/null +++ b/fmt_junit_test.go @@ -0,0 +1,169 @@ +package godog + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "strings" + "testing" + + "github.com/DATA-DOG/godog/colors" + "github.com/DATA-DOG/godog/gherkin" +) + +var sampleGherkinFeature = ` +Feature: junit formatter + + Background: + Given passing + + Scenario: passing scenario + Then passing + + Scenario: failing scenario + When failing + Then passing + + Scenario: pending scenario + When pending + Then passing + + Scenario: undefined scenario + When undefined + Then next undefined + + Scenario Outline: outline + Given + When + + Examples: + | one | two | + | passing | passing | + | passing | failing | + | passing | pending | + + Examples: + | one | two | + | passing | undefined | +` + +func TestJUnitSimpleFeature(t *testing.T) { + feat, err := gherkin.ParseFeature(strings.NewReader(sampleGherkinFeature)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var buf bytes.Buffer + w := colors.Uncolored(&buf) + s := &Suite{ + fmt: junitFunc("junit", w), + features: []*feature{&feature{ + Path: "any.feature", + Feature: feat, + Content: []byte(sampleGherkinFeature), + }}, + } + + s.Step(`^passing$`, func() error { return nil }) + s.Step(`^failing$`, func() error { return fmt.Errorf("errored") }) + s.Step(`^pending$`, func() error { return ErrPending }) + + expected := junitPackageSuite{ + Name: "junit", + Tests: 8, + Skipped: 0, + Failures: 2, + Errors: 4, + Time: "0s", + TestSuites: []*junitTestSuite{{ + Name: "junit formatter", + Tests: 8, + Skipped: 0, + Failures: 2, + Errors: 4, + Time: "0s", + TestCases: []*junitTestCase{ + { + Name: "passing scenario", + Status: "passed", + Time: "0s", + }, + { + Name: "failing scenario", + Status: "failed", + Time: "0s", + Failure: &junitFailure{ + Message: "Step failing: errored", + }, + Error: []*junitError{ + {Message: "Step passing", Type: "skipped"}, + }, + }, + { + Name: "pending scenario", + Status: "pending", + Time: "0s", + Error: []*junitError{ + {Message: "Step pending: TODO: write pending definition", Type: "pending"}, + {Message: "Step passing", Type: "skipped"}, + }, + }, + { + Name: "undefined scenario", + Status: "undefined", + Time: "0s", + Error: []*junitError{ + {Message: "Step undefined", Type: "undefined"}, + {Message: "Step next undefined", Type: "undefined"}, + }, + }, + { + Name: "outline #1", + Status: "passed", + Time: "0s", + }, + { + Name: "outline #2", + Status: "failed", + Time: "0s", + Failure: &junitFailure{ + Message: "Step failing: errored", + }, + }, + { + Name: "outline #3", + Status: "pending", + Time: "0s", + Error: []*junitError{ + {Message: "Step pending: TODO: write pending definition", Type: "pending"}, + }, + }, + { + Name: "outline #4", + Status: "undefined", + Time: "0s", + Error: []*junitError{ + {Message: "Step undefined", Type: "undefined"}, + }, + }, + }, + }}, + } + + s.run() + s.fmt.Summary() + + var exp bytes.Buffer + io.WriteString(&exp, xml.Header) + + enc := xml.NewEncoder(&exp) + enc.Indent("", " ") + if err := enc.Encode(expected); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if buf.String() != exp.String() { + t.Fatalf("expected output does not match: %s", buf.String()) + } +} diff --git a/suite_test.go b/suite_test.go index 1ccae7f..d27174c 100644 --- a/suite_test.go +++ b/suite_test.go @@ -415,9 +415,8 @@ func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error { } var actual []cukeFeatureJSON - re := regexp.MustCompile(`"duration":\s*\d+`) - replaced := re.ReplaceAllString(s.out.String(), `"duration": -1`) - if err := json.Unmarshal([]byte(loc.ReplaceAllString(replaced, `"suite_test.go:0"`)), &actual); err != nil { + replaced := loc.ReplaceAllString(s.out.String(), `"suite_test.go:0"`) + if err := json.Unmarshal([]byte(replaced), &actual); err != nil { return err } diff --git a/utils_test.go b/utils_test.go index 50373be..dda2d74 100644 --- a/utils_test.go +++ b/utils_test.go @@ -5,6 +5,9 @@ import ( "time" ) +// this zeroes the time throughout whole test suite +// and makes it easier to assert output +// activated only when godog tests are being run func init() { timeNowFunc = func() time.Time { return time.Time{}