diff --git a/CHANGELOG.md b/CHANGELOG.md index 06e985d..6d485f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,12 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt ### Added - Support for step definitions without return ([364](https://github.com/cucumber/godog/pull/364) - [titouanfreville]) -- Contextualized hooks for scenarios and steps ([409](https://github.com/cucumber/godog/pull/409)) - [vearutop]) -- Step result status in After hook ([409](https://github.com/cucumber/godog/pull/409)) - [vearutop]) -- Support auto converting doc strings to plain strings ([380](https://github.com/cucumber/godog/pull/380)) - [chirino]) -- Use multiple formatters in the same test run ([392](https://github.com/cucumber/godog/pull/392)) - [vearutop]) -- Added `RetrieveFeatures()` method to `godog.TestSuite` ([276](https://github.com/cucumber/godog/pull/276)) - [radtriste]) +- Contextualized hooks for scenarios and steps ([409](https://github.com/cucumber/godog/pull/409) - [vearutop]) +- Step result status in After hook ([409](https://github.com/cucumber/godog/pull/409) - [vearutop]) +- Support auto converting doc strings to plain strings ([380](https://github.com/cucumber/godog/pull/380) - [chirino]) +- Use multiple formatters in the same test run ([392](https://github.com/cucumber/godog/pull/392) - [vearutop]) +- Added `RetrieveFeatures()` method to `godog.TestSuite` ([276](https://github.com/cucumber/godog/pull/276) - [radtriste]) +- Added support to create custom formatters ([372](https://github.com/cucumber/godog/pull/372) - [leviable]) ### Changed diff --git a/_examples/custom-formatter/README.md b/_examples/custom-formatter/README.md new file mode 100644 index 0000000..31fa7be --- /dev/null +++ b/_examples/custom-formatter/README.md @@ -0,0 +1,19 @@ + +# Custom Formatter Example + +This example custom formatter demonstrates some ways to build and use custom formatters with godog + + +## Emoji Progress + +The first example is the Emoji formatter, built on top of the Progress formatter that is included with godog. + +To run it: + +``` +$ godog -f emoji +``` + +Which would output step progress as emojis rather than text: + +![](imgs/emoji-output-example.png) \ No newline at end of file diff --git a/_examples/custom-formatter/custom.go b/_examples/custom-formatter/custom.go deleted file mode 100644 index 9e364c3..0000000 --- a/_examples/custom-formatter/custom.go +++ /dev/null @@ -1,32 +0,0 @@ -package customformatter - -import ( - "io" - - "github.com/cucumber/godog" - "github.com/cucumber/messages-go/v16" -) - -func init() { - godog.Format("custom", "Custom formatter", customFormatterFunc) -} - -func customFormatterFunc(suite string, out io.Writer) godog.Formatter { - return &customFmt{suiteName: suite, out: out} -} - -type customFmt struct { - suiteName string - out io.Writer -} - -func (f *customFmt) TestRunStarted() {} -func (f *customFmt) Feature(*messages.GherkinDocument, string, []byte) {} -func (f *customFmt) Pickle(*godog.Scenario) {} -func (f *customFmt) Defined(*godog.Scenario, *godog.Step, *godog.StepDefinition) {} -func (f *customFmt) Passed(*godog.Scenario, *godog.Step, *godog.StepDefinition) {} -func (f *customFmt) Skipped(*godog.Scenario, *godog.Step, *godog.StepDefinition) {} -func (f *customFmt) Undefined(*godog.Scenario, *godog.Step, *godog.StepDefinition) {} -func (f *customFmt) Failed(*godog.Scenario, *godog.Step, *godog.StepDefinition, error) {} -func (f *customFmt) Pending(*godog.Scenario, *godog.Step, *godog.StepDefinition) {} -func (f *customFmt) Summary() {} diff --git a/_examples/custom-formatter/emoji.go b/_examples/custom-formatter/emoji.go new file mode 100644 index 0000000..50cc5d5 --- /dev/null +++ b/_examples/custom-formatter/emoji.go @@ -0,0 +1,122 @@ +package main + +import ( + "fmt" + "io" + "math" + + "github.com/cucumber/godog" +) + +const ( + passedEmoji = "✅" + skippedEmoji = "➖" + failedEmoji = "❌" + undefinedEmoji = "❓" + pendingEmoji = "🚧" +) + +func init() { + godog.Format("emoji", "Progress formatter with emojis", emojiFormatterFunc) +} + +func emojiFormatterFunc(suite string, out io.Writer) godog.Formatter { + return newEmojiFmt(suite, out) +} + +func newEmojiFmt(suite string, out io.Writer) *emojiFmt { + return &emojiFmt{ + ProgressFmt: godog.NewProgressFmt(suite, out), + out: out, + } +} + +type emojiFmt struct { + *godog.ProgressFmt + + out io.Writer +} + +func (f *emojiFmt) TestRunStarted() {} + +func (f *emojiFmt) Passed(scenario *godog.Scenario, step *godog.Step, match *godog.StepDefinition) { + f.ProgressFmt.Base.Passed(scenario, step, match) + + f.ProgressFmt.Base.Lock.Lock() + defer f.ProgressFmt.Base.Lock.Unlock() + + f.step(step.Id) +} + +func (f *emojiFmt) Skipped(scenario *godog.Scenario, step *godog.Step, match *godog.StepDefinition) { + f.ProgressFmt.Base.Skipped(scenario, step, match) + + f.ProgressFmt.Base.Lock.Lock() + defer f.ProgressFmt.Base.Lock.Unlock() + + f.step(step.Id) +} + +func (f *emojiFmt) Undefined(scenario *godog.Scenario, step *godog.Step, match *godog.StepDefinition) { + f.ProgressFmt.Base.Undefined(scenario, step, match) + + f.ProgressFmt.Base.Lock.Lock() + defer f.ProgressFmt.Base.Lock.Unlock() + + f.step(step.Id) +} + +func (f *emojiFmt) Failed(scenario *godog.Scenario, step *godog.Step, match *godog.StepDefinition, err error) { + f.ProgressFmt.Base.Failed(scenario, step, match, err) + + f.ProgressFmt.Base.Lock.Lock() + defer f.ProgressFmt.Base.Lock.Unlock() + + f.step(step.Id) +} + +func (f *emojiFmt) Pending(scenario *godog.Scenario, step *godog.Step, match *godog.StepDefinition) { + f.ProgressFmt.Base.Pending(scenario, step, match) + + f.ProgressFmt.Base.Lock.Lock() + defer f.ProgressFmt.Base.Lock.Unlock() + + f.step(step.Id) +} + +func (f *emojiFmt) Summary() { + f.printSummaryLegend() + f.ProgressFmt.Summary() +} + +func (f *emojiFmt) printSummaryLegend() { + fmt.Fprint(f.out, "\n\nOutput Legend:\n") + fmt.Fprint(f.out, fmt.Sprintf("\t%s Passed\n", passedEmoji)) + fmt.Fprint(f.out, fmt.Sprintf("\t%s Failed\n", failedEmoji)) + fmt.Fprint(f.out, fmt.Sprintf("\t%s Skipped\n", skippedEmoji)) + fmt.Fprint(f.out, fmt.Sprintf("\t%s Undefined\n", undefinedEmoji)) + fmt.Fprint(f.out, fmt.Sprintf("\t%s Pending\n", pendingEmoji)) +} + +func (f *emojiFmt) step(pickleStepID string) { + pickleStepResult := f.Storage.MustGetPickleStepResult(pickleStepID) + + switch pickleStepResult.Status { + case godog.StepPassed: + fmt.Fprint(f.out, fmt.Sprintf(" %s", passedEmoji)) + case godog.StepSkipped: + fmt.Fprint(f.out, fmt.Sprintf(" %s", skippedEmoji)) + case godog.StepFailed: + fmt.Fprint(f.out, fmt.Sprintf(" %s", failedEmoji)) + case godog.StepUndefined: + fmt.Fprint(f.out, fmt.Sprintf(" %s", undefinedEmoji)) + case godog.StepPending: + fmt.Fprint(f.out, fmt.Sprintf(" %s", pendingEmoji)) + } + + *f.Steps++ + + if math.Mod(float64(*f.Steps), float64(f.StepsPerRow)) == 0 { + fmt.Fprintf(f.out, " %d\n", *f.Steps) + } +} diff --git a/_examples/custom-formatter/features/emoji.feature b/_examples/custom-formatter/features/emoji.feature new file mode 100644 index 0000000..8b27e53 --- /dev/null +++ b/_examples/custom-formatter/features/emoji.feature @@ -0,0 +1,26 @@ +# file: $GOPATH/godogs/features/godogs.feature +Feature: Custom emoji formatter examples + In order to be happy + As a hungry gopher + I need to be able to eat godogs + + Scenario: Passing test + Given there are 12 godogs + When I eat 5 + Then there should be 7 remaining + + Scenario: Failing and Skipped test + Given there are 12 godogs + When I eat 5 + Then there should be 6 remaining + And there should be 4 remaining + + Scenario: Undefined steps + Given there are 12 godogs + When I eat 5 + Then this step is not defined + + Scenario: Pending step + Given there are 12 godogs + When I eat 5 + Then this step is pending diff --git a/_examples/custom-formatter/go.mod b/_examples/custom-formatter/go.mod index b46595f..5a88259 100644 --- a/_examples/custom-formatter/go.mod +++ b/_examples/custom-formatter/go.mod @@ -3,10 +3,11 @@ module custom-formatter go 1.14 require ( - github.com/cucumber/gherkin-go/v11 v11.0.0 // indirect github.com/cucumber/godog v0.10.1-0.20210705192606-df8c6e49b40b - github.com/cucumber/messages-go/v10 v10.0.3 // indirect - github.com/cucumber/messages-go/v16 v16.0.1 github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.2 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/spf13/pflag v1.0.5 ) + +replace github.com/cucumber/godog => ../.. diff --git a/_examples/custom-formatter/go.sum b/_examples/custom-formatter/go.sum index 57ee6fb..fd61262 100644 --- a/_examples/custom-formatter/go.sum +++ b/_examples/custom-formatter/go.sum @@ -19,7 +19,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aslakhellesoy/gox v1.0.100/go.mod h1:AJl542QsKKG96COVsv0N74HHzVQgDIQPceVUh1aeU2M= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -33,19 +32,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cucumber/gherkin-go/v11 v11.0.0 h1:cwVwN1Qn2VRSfHZNLEh5x00tPBmZcjATBWDpxsR5Xug= -github.com/cucumber/gherkin-go/v11 v11.0.0/go.mod h1:CX33k2XU2qog4e+TFjOValoq6mIUq0DmVccZs238R9w= github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= -github.com/cucumber/godog v0.10.1-0.20210705192606-df8c6e49b40b h1:TthfOM7c3wz2M5kUvktCU27WXufvUSV/YtcxQk2yaq8= -github.com/cucumber/godog v0.10.1-0.20210705192606-df8c6e49b40b/go.mod h1:ql2U1OH5nlLZ2UDD/3fDJ1+0vkib0XGgEn8NYXCwDZQ= -github.com/cucumber/godog v0.11.0 h1:xgaWyJuAD6A+aW4TfVGNDBhuMyKW0jjl0cvY3KNxEak= -github.com/cucumber/godog v0.11.0/go.mod h1:GyxCIrsg1sgEgpL2GD/rMr3fIoNHpgkjm9nANw/89XY= -github.com/cucumber/messages-go/v10 v10.0.1/go.mod h1:kA5T38CBlBbYLU12TIrJ4fk4wSkVVOgyh7Enyy8WnSg= -github.com/cucumber/messages-go/v10 v10.0.3/go.mod h1:9jMZ2Y8ZxjLY6TG2+x344nt5rXstVVDYSdS5ySfI1WY= -github.com/cucumber/messages-go/v16 v10.0.1/go.mod h1:kA5T38CBlBbYLU12TIrJ4fk4wSkVVOgyh7Enyy8WnSg= -github.com/cucumber/messages-go/v16 v10.0.3 h1:m/9SD/K/A15WP7i1aemIv7cwvUw+viS51Ui5HBw1cdE= -github.com/cucumber/messages-go/v16 v10.0.3/go.mod h1:9jMZ2Y8ZxjLY6TG2+x344nt5rXstVVDYSdS5ySfI1WY= github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= @@ -62,14 +50,10 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -88,6 +72,7 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -98,11 +83,9 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU= github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= github.com/hashicorp/go-memdb v1.3.2 h1:RBKHOsnSszpU6vxq80LzC2BaQjuuvoyaQbkLTf7V7g8= github.com/hashicorp/go-memdb v1.3.2/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= @@ -115,7 +98,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -130,15 +112,14 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -161,7 +142,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -186,7 +166,9 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -202,9 +184,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -281,7 +260,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -322,8 +300,6 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -331,7 +307,6 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/_examples/custom-formatter/godogs.go b/_examples/custom-formatter/godogs.go new file mode 100644 index 0000000..e71f308 --- /dev/null +++ b/_examples/custom-formatter/godogs.go @@ -0,0 +1,6 @@ +package main + +// Godogs available to eat +var Godogs int + +func main() { /* usual main func */ } diff --git a/_examples/custom-formatter/godogs_test.go b/_examples/custom-formatter/godogs_test.go new file mode 100644 index 0000000..9523929 --- /dev/null +++ b/_examples/custom-formatter/godogs_test.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/cucumber/godog" + "github.com/cucumber/godog/colors" + flag "github.com/spf13/pflag" +) + +var opts = godog.Options{ + Output: colors.Colored(os.Stdout), + Format: "emoji", +} + +func init() { + godog.BindCommandLineFlags("godog.", &opts) +} + +func TestMain(m *testing.M) { + flag.Parse() + opts.Paths = flag.Args() + + status := godog.TestSuite{ + Name: "godogs", + TestSuiteInitializer: InitializeTestSuite, + ScenarioInitializer: InitializeScenario, + Options: &opts, + }.Run() + + os.Exit(status) +} + +func thereAreGodogs(available int) error { + Godogs = available + return nil +} + +func iEat(num int) error { + if Godogs < num { + return fmt.Errorf("you cannot eat %d godogs, there are %d available", num, Godogs) + } + Godogs -= num + return nil +} + +func thereShouldBeRemaining(remaining int) error { + if Godogs != remaining { + return fmt.Errorf("expected %d godogs to be remaining, but there is %d", remaining, Godogs) + } + return nil +} +func thisStepIsPending() error { + return godog.ErrPending +} + +func InitializeTestSuite(ctx *godog.TestSuiteContext) { + ctx.BeforeSuite(func() { Godogs = 0 }) +} + +func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + Godogs = 0 // clean the state before every scenario + + return ctx, nil + }) + + ctx.Step(`^there are (\d+) godogs$`, thereAreGodogs) + ctx.Step(`^I eat (\d+)$`, iEat) + ctx.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining) + ctx.Step(`^this step is pending$`, thisStepIsPending) +} diff --git a/_examples/custom-formatter/imgs/emoji-output-example.png b/_examples/custom-formatter/imgs/emoji-output-example.png new file mode 100644 index 0000000..cf597e2 Binary files /dev/null and b/_examples/custom-formatter/imgs/emoji-output-example.png differ diff --git a/fmt.go b/fmt.go index 5934ddb..f30f9f8 100644 --- a/fmt.go +++ b/fmt.go @@ -74,3 +74,51 @@ func printStepDefinitions(steps []*models.StepDefinition, w io.Writer) { fmt.Fprintln(w, "there were no contexts registered, could not find any step definition..") } } + +// NewBaseFmt creates a new base formatter. +func NewBaseFmt(suite string, out io.Writer) *BaseFmt { + return internal_fmt.NewBase(suite, out) +} + +// NewProgressFmt creates a new progress formatter. +func NewProgressFmt(suite string, out io.Writer) *ProgressFmt { + return internal_fmt.NewProgress(suite, out) +} + +// NewPrettyFmt creates a new pretty formatter. +func NewPrettyFmt(suite string, out io.Writer) *PrettyFmt { + return &PrettyFmt{Base: NewBaseFmt(suite, out)} +} + +// NewEventsFmt creates a new event streaming formatter. +func NewEventsFmt(suite string, out io.Writer) *EventsFmt { + return &EventsFmt{Base: NewBaseFmt(suite, out)} +} + +// NewCukeFmt creates a new Cucumber JSON formatter. +func NewCukeFmt(suite string, out io.Writer) *CukeFmt { + return &CukeFmt{Base: NewBaseFmt(suite, out)} +} + +// NewJUnitFmt creates a new JUnit formatter. +func NewJUnitFmt(suite string, out io.Writer) *JUnitFmt { + return &JUnitFmt{Base: NewBaseFmt(suite, out)} +} + +// BaseFmt exports Base formatter. +type BaseFmt = internal_fmt.Base + +// ProgressFmt exports Progress formatter. +type ProgressFmt = internal_fmt.Progress + +// PrettyFmt exports Pretty formatter. +type PrettyFmt = internal_fmt.Pretty + +// EventsFmt exports Events formatter. +type EventsFmt = internal_fmt.Events + +// CukeFmt exports Cucumber JSON formatter. +type CukeFmt = internal_fmt.Cuke + +// JUnitFmt exports JUnit formatter. +type JUnitFmt = internal_fmt.JUnit diff --git a/internal/formatters/fmt_base.go b/internal/formatters/fmt_base.go index 9088968..c95a97f 100644 --- a/internal/formatters/fmt_base.go +++ b/internal/formatters/fmt_base.go @@ -20,82 +20,82 @@ import ( "github.com/cucumber/godog/internal/utils" ) -// BaseFormatterFunc implements the FormatterFunc for the base formatter +// BaseFormatterFunc implements the FormatterFunc for the base formatter. func BaseFormatterFunc(suite string, out io.Writer) formatters.Formatter { - return NewBaseFmt(suite, out) + return NewBase(suite, out) } -// NewBaseFmt creates a new base formatter -func NewBaseFmt(suite string, out io.Writer) *Basefmt { - return &Basefmt{ +// NewBase creates a new base formatter. +func NewBase(suite string, out io.Writer) *Base { + return &Base{ suiteName: suite, indent: 2, out: out, - lock: new(sync.Mutex), + Lock: new(sync.Mutex), } } -// Basefmt ... -type Basefmt struct { +// Base is a base formatter. +type Base struct { suiteName string out io.Writer indent int - storage *storage.Storage - lock *sync.Mutex + Storage *storage.Storage + Lock *sync.Mutex } -// SetStorage ... -func (f *Basefmt) SetStorage(st *storage.Storage) { - f.lock.Lock() - defer f.lock.Unlock() +// SetStorage assigns gherkin data storage. +func (f *Base) SetStorage(st *storage.Storage) { + f.Lock.Lock() + defer f.Lock.Unlock() - f.storage = st + f.Storage = st } -// TestRunStarted ... -func (f *Basefmt) TestRunStarted() {} +// TestRunStarted is triggered on test start. +func (f *Base) TestRunStarted() {} -// Feature ... -func (f *Basefmt) Feature(*messages.GherkinDocument, string, []byte) {} +// Feature receives gherkin document. +func (f *Base) Feature(*messages.GherkinDocument, string, []byte) {} -// Pickle ... -func (f *Basefmt) Pickle(*messages.Pickle) {} +// Pickle receives scenario. +func (f *Base) Pickle(*messages.Pickle) {} -// Defined ... -func (f *Basefmt) Defined(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { +// Defined receives step definition. +func (f *Base) Defined(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { } -// Passed ... -func (f *Basefmt) Passed(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) {} +// Passed captures passed step. +func (f *Base) Passed(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) {} -// Skipped ... -func (f *Basefmt) Skipped(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { +// Skipped captures skipped step. +func (f *Base) Skipped(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { } -// Undefined ... -func (f *Basefmt) Undefined(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { +// Undefined captures undefined step. +func (f *Base) Undefined(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { } -// Failed ... -func (f *Basefmt) Failed(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition, error) { +// Failed captures failed step. +func (f *Base) Failed(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition, error) { } -// Pending ... -func (f *Basefmt) Pending(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { +// Pending captures pending step. +func (f *Base) Pending(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) { } -// Summary ... -func (f *Basefmt) Summary() { +// Summary renders summary information. +func (f *Base) Summary() { var totalSc, passedSc, undefinedSc int var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int - pickleResults := f.storage.MustGetPickleResults() + pickleResults := f.Storage.MustGetPickleResults() for _, pr := range pickleResults { var prStatus models.StepResultStatus totalSc++ - pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pr.PickleID) + pickleStepResults := f.Storage.MustGetPickleStepResultsByPickleID(pr.PickleID) if len(pickleStepResults) == 0 { prStatus = undefined @@ -156,7 +156,7 @@ func (f *Basefmt) Summary() { } scenarios = append(scenarios, parts...) - testRunStartedAt := f.storage.MustGetTestRunStarted().StartedAt + testRunStartedAt := f.Storage.MustGetTestRunStarted().StartedAt elapsed := utils.TimeNowFunc().Sub(testRunStartedAt) fmt.Fprintln(f.out, "") @@ -194,9 +194,9 @@ func (f *Basefmt) Summary() { } } -// Snippets ... -func (f *Basefmt) Snippets() string { - undefinedStepResults := f.storage.MustGetPickleStepResultsByStatus(undefined) +// Snippets returns code suggestions for undefined steps. +func (f *Base) Snippets() string { + undefinedStepResults := f.Storage.MustGetPickleStepResultsByStatus(undefined) if len(undefinedStepResults) == 0 { return "" } @@ -205,7 +205,7 @@ func (f *Basefmt) Snippets() string { var snips []undefinedSnippet // build snippets for _, u := range undefinedStepResults { - pickleStep := f.storage.MustGetPickleStep(u.PickleStepID) + pickleStep := f.Storage.MustGetPickleStep(u.PickleStepID) steps := []string{pickleStep.Text} arg := pickleStep.Argument diff --git a/internal/formatters/fmt_cucumber.go b/internal/formatters/fmt_cucumber.go index 04b55ad..fee855a 100644 --- a/internal/formatters/fmt_cucumber.go +++ b/internal/formatters/fmt_cucumber.go @@ -30,15 +30,17 @@ func init() { // CucumberFormatterFunc implements the FormatterFunc for the cucumber formatter func CucumberFormatterFunc(suite string, out io.Writer) formatters.Formatter { - return &cukefmt{Basefmt: NewBaseFmt(suite, out)} + return &Cuke{Base: NewBase(suite, out)} } -type cukefmt struct { - *Basefmt +// Cuke ... +type Cuke struct { + *Base } -func (f *cukefmt) Summary() { - features := f.storage.MustGetFeatures() +// Summary renders test result as Cucumber JSON. +func (f *Cuke) Summary() { + features := f.Storage.MustGetFeatures() res := f.buildCukeFeatures(features) @@ -50,7 +52,7 @@ func (f *cukefmt) Summary() { fmt.Fprintf(f.out, "%s\n", string(dat)) } -func (f *cukefmt) buildCukeFeatures(features []*models.Feature) (res []CukeFeatureJSON) { +func (f *Cuke) buildCukeFeatures(features []*models.Feature) (res []CukeFeatureJSON) { sort.Sort(sortFeaturesByName(features)) res = make([]CukeFeatureJSON, len(features)) @@ -58,7 +60,7 @@ func (f *cukefmt) buildCukeFeatures(features []*models.Feature) (res []CukeFeatu for idx, feat := range features { cukeFeature := buildCukeFeature(feat) - pickles := f.storage.MustGetPickles(feat.Uri) + pickles := f.Storage.MustGetPickles(feat.Uri) sort.Sort(sortPicklesByID(pickles)) cukeFeature.Elements = f.buildCukeElements(pickles) @@ -75,12 +77,12 @@ func (f *cukefmt) buildCukeFeatures(features []*models.Feature) (res []CukeFeatu return res } -func (f *cukefmt) buildCukeElements(pickles []*messages.Pickle) (res []cukeElement) { +func (f *Cuke) buildCukeElements(pickles []*messages.Pickle) (res []cukeElement) { res = make([]cukeElement, len(pickles)) for idx, pickle := range pickles { - pickleResult := f.storage.MustGetPickleResult(pickle.Id) - pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) + pickleResult := f.Storage.MustGetPickleResult(pickle.Id) + pickleStepResults := f.Storage.MustGetPickleStepResultsByPickleID(pickle.Id) cukeElement := f.buildCukeElement(pickle) @@ -201,8 +203,8 @@ func buildCukeFeature(feat *models.Feature) CukeFeatureJSON { return cukeFeature } -func (f *cukefmt) buildCukeElement(pickle *messages.Pickle) (cukeElement cukeElement) { - feature := f.storage.MustGetFeature(pickle.Uri) +func (f *Cuke) buildCukeElement(pickle *messages.Pickle) (cukeElement cukeElement) { + feature := f.Storage.MustGetFeature(pickle.Uri) scenario := feature.FindScenario(pickle.AstNodeIds[0]) cukeElement.Name = pickle.Name @@ -245,9 +247,9 @@ func (f *cukefmt) buildCukeElement(pickle *messages.Pickle) (cukeElement cukeEle return cukeElement } -func (f *cukefmt) buildCukeStep(pickle *messages.Pickle, stepResult models.PickleStepResult) (cukeStep cukeStep) { - feature := f.storage.MustGetFeature(pickle.Uri) - pickleStep := f.storage.MustGetPickleStep(stepResult.PickleStepID) +func (f *Cuke) buildCukeStep(pickle *messages.Pickle, stepResult models.PickleStepResult) (cukeStep cukeStep) { + feature := f.Storage.MustGetFeature(pickle.Uri) + pickleStep := f.Storage.MustGetPickleStep(stepResult.PickleStepID) step := feature.FindStep(pickleStep.AstNodeIds[0]) line := step.Location.Line diff --git a/internal/formatters/fmt_events.go b/internal/formatters/fmt_events.go index 955b653..879a3c2 100644 --- a/internal/formatters/fmt_events.go +++ b/internal/formatters/fmt_events.go @@ -20,14 +20,15 @@ func init() { // EventsFormatterFunc implements the FormatterFunc for the events formatter func EventsFormatterFunc(suite string, out io.Writer) formatters.Formatter { - return &eventsFormatter{Basefmt: NewBaseFmt(suite, out)} + return &Events{Base: NewBase(suite, out)} } -type eventsFormatter struct { - *Basefmt +// Events - Events formatter +type Events struct { + *Base } -func (f *eventsFormatter) event(ev interface{}) { +func (f *Events) event(ev interface{}) { data, err := json.Marshal(ev) if err != nil { panic(fmt.Sprintf("failed to marshal stream event: %+v - %v", ev, err)) @@ -35,11 +36,12 @@ func (f *eventsFormatter) event(ev interface{}) { fmt.Fprintln(f.out, string(data)) } -func (f *eventsFormatter) Pickle(pickle *messages.Pickle) { - f.Basefmt.Pickle(pickle) +// Pickle receives scenario. +func (f *Events) Pickle(pickle *messages.Pickle) { + f.Base.Pickle(pickle) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.event(&struct { Event string `json:"event"` @@ -68,11 +70,12 @@ func (f *eventsFormatter) Pickle(pickle *messages.Pickle) { } } -func (f *eventsFormatter) TestRunStarted() { - f.Basefmt.TestRunStarted() +// TestRunStarted is triggered on test start. +func (f *Events) TestRunStarted() { + f.Base.TestRunStarted() - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.event(&struct { Event string `json:"event"` @@ -87,11 +90,12 @@ func (f *eventsFormatter) TestRunStarted() { }) } -func (f *eventsFormatter) Feature(ft *messages.GherkinDocument, p string, c []byte) { - f.Basefmt.Feature(ft, p, c) +// Feature receives gherkin document. +func (f *Events) Feature(ft *messages.GherkinDocument, p string, c []byte) { + f.Base.Feature(ft, p, c) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.event(&struct { Event string `json:"event"` @@ -104,16 +108,17 @@ func (f *eventsFormatter) Feature(ft *messages.GherkinDocument, p string, c []by }) } -func (f *eventsFormatter) Summary() { +// Summary pushes summary information to JSON stream. +func (f *Events) Summary() { // @TODO: determine status status := passed - f.storage.MustGetPickleStepResultsByStatus(failed) + f.Storage.MustGetPickleStepResultsByStatus(failed) - if len(f.storage.MustGetPickleStepResultsByStatus(failed)) > 0 { + if len(f.Storage.MustGetPickleStepResultsByStatus(failed)) > 0 { status = failed - } else if len(f.storage.MustGetPickleStepResultsByStatus(passed)) == 0 { - if len(f.storage.MustGetPickleStepResultsByStatus(undefined)) > len(f.storage.MustGetPickleStepResultsByStatus(pending)) { + } else if len(f.Storage.MustGetPickleStepResultsByStatus(passed)) == 0 { + if len(f.Storage.MustGetPickleStepResultsByStatus(undefined)) > len(f.Storage.MustGetPickleStepResultsByStatus(pending)) { status = undefined } else { status = pending @@ -140,9 +145,9 @@ func (f *eventsFormatter) Summary() { }) } -func (f *eventsFormatter) step(pickle *messages.Pickle, pickleStep *messages.PickleStep) { - feature := f.storage.MustGetFeature(pickle.Uri) - pickleStepResult := f.storage.MustGetPickleStepResult(pickleStep.Id) +func (f *Events) step(pickle *messages.Pickle, pickleStep *messages.PickleStep) { + feature := f.Storage.MustGetFeature(pickle.Uri) + pickleStepResult := f.Storage.MustGetPickleStepResult(pickleStep.Id) step := feature.FindStep(pickleStep.AstNodeIds[0]) var errMsg string @@ -166,7 +171,7 @@ func (f *eventsFormatter) step(pickle *messages.Pickle, pickleStep *messages.Pic if isLastStep(pickle, pickleStep) { var status string - pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) + pickleStepResults := f.Storage.MustGetPickleStepResultsByPickleID(pickle.Id) for _, stepResult := range pickleStepResults { switch stepResult.Status { case passed, failed, undefined, pending: @@ -188,17 +193,18 @@ func (f *eventsFormatter) step(pickle *messages.Pickle, pickleStep *messages.Pic } } -func (f *eventsFormatter) Defined(pickle *messages.Pickle, pickleStep *messages.PickleStep, def *formatters.StepDefinition) { - f.Basefmt.Defined(pickle, pickleStep, def) +// Defined receives step definition. +func (f *Events) Defined(pickle *messages.Pickle, pickleStep *messages.PickleStep, def *formatters.StepDefinition) { + f.Base.Defined(pickle, pickleStep, def) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() - feature := f.storage.MustGetFeature(pickle.Uri) + feature := f.Storage.MustGetFeature(pickle.Uri) step := feature.FindStep(pickleStep.AstNodeIds[0]) if def != nil { - matchedDef := f.storage.MustGetStepDefintionMatch(pickleStep.AstNodeIds[0]) + matchedDef := f.Storage.MustGetStepDefintionMatch(pickleStep.AstNodeIds[0]) m := def.Expr.FindStringSubmatchIndex(pickleStep.Text)[2:] var args [][2]int @@ -238,53 +244,58 @@ func (f *eventsFormatter) Defined(pickle *messages.Pickle, pickleStep *messages. }) } -func (f *eventsFormatter) Passed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Passed(pickle, step, match) +// Passed captures passed step. +func (f *Events) Passed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Passed(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(pickle, step) } -func (f *eventsFormatter) Skipped(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Skipped(pickle, step, match) +// Skipped captures skipped step. +func (f *Events) Skipped(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Skipped(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(pickle, step) } -func (f *eventsFormatter) Undefined(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Undefined(pickle, step, match) +// Undefined captures undefined step. +func (f *Events) Undefined(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Undefined(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(pickle, step) } -func (f *eventsFormatter) Failed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) { - f.Basefmt.Failed(pickle, step, match, err) +// Failed captures failed step. +func (f *Events) Failed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) { + f.Base.Failed(pickle, step, match, err) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(pickle, step) } -func (f *eventsFormatter) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Pending(pickle, step, match) +// Pending captures pending step. +func (f *Events) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Pending(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(pickle, step) } -func (f *eventsFormatter) scenarioLocation(pickle *messages.Pickle) string { - feature := f.storage.MustGetFeature(pickle.Uri) +func (f *Events) scenarioLocation(pickle *messages.Pickle) string { + feature := f.Storage.MustGetFeature(pickle.Uri) scenario := feature.FindScenario(pickle.AstNodeIds[0]) line := scenario.Location.Line diff --git a/internal/formatters/fmt_junit.go b/internal/formatters/fmt_junit.go index 8beab0d..bc6ed27 100644 --- a/internal/formatters/fmt_junit.go +++ b/internal/formatters/fmt_junit.go @@ -19,14 +19,16 @@ func init() { // JUnitFormatterFunc implements the FormatterFunc for the junit formatter func JUnitFormatterFunc(suite string, out io.Writer) formatters.Formatter { - return &junitFormatter{Basefmt: NewBaseFmt(suite, out)} + return &JUnit{Base: NewBase(suite, out)} } -type junitFormatter struct { - *Basefmt +// JUnit renders test results in JUnit format. +type JUnit struct { + *Base } -func (f *junitFormatter) Summary() { +// Summary renders summary information. +func (f *JUnit) Summary() { suite := f.buildJUNITPackageSuite() _, err := io.WriteString(f.out, xml.Header) @@ -45,11 +47,11 @@ func junitTimeDuration(from, to time.Time) string { return strconv.FormatFloat(to.Sub(from).Seconds(), 'f', -1, 64) } -func (f *junitFormatter) buildJUNITPackageSuite() JunitPackageSuite { - features := f.storage.MustGetFeatures() +func (f *JUnit) buildJUNITPackageSuite() JunitPackageSuite { + features := f.Storage.MustGetFeatures() sort.Sort(sortFeaturesByName(features)) - testRunStartedAt := f.storage.MustGetTestRunStarted().StartedAt + testRunStartedAt := f.Storage.MustGetTestRunStarted().StartedAt suite := JunitPackageSuite{ Name: f.suiteName, @@ -58,7 +60,7 @@ func (f *junitFormatter) buildJUNITPackageSuite() JunitPackageSuite { } for idx, feature := range features { - pickles := f.storage.MustGetPickles(feature.Uri) + pickles := f.Storage.MustGetPickles(feature.Uri) sort.Sort(sortPicklesByID(pickles)) ts := junitTestSuite{ @@ -78,7 +80,7 @@ func (f *junitFormatter) buildJUNITPackageSuite() JunitPackageSuite { for idx, pickle := range pickles { tc := junitTestCase{} - pickleResult := f.storage.MustGetPickleResult(pickle.Id) + pickleResult := f.Storage.MustGetPickleResult(pickle.Id) if idx == 0 { firstPickleStartedAt = pickleResult.StartedAt @@ -88,7 +90,7 @@ func (f *junitFormatter) buildJUNITPackageSuite() JunitPackageSuite { if len(pickle.Steps) > 0 { lastStep := pickle.Steps[len(pickle.Steps)-1] - lastPickleStepResult := f.storage.MustGetPickleStepResult(lastStep.Id) + lastPickleStepResult := f.Storage.MustGetPickleStepResult(lastStep.Id) lastPickleFinishedAt = lastPickleStepResult.FinishedAt } @@ -103,9 +105,9 @@ func (f *junitFormatter) buildJUNITPackageSuite() JunitPackageSuite { ts.Tests++ suite.Tests++ - pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) + pickleStepResults := f.Storage.MustGetPickleStepResultsByPickleID(pickle.Id) for _, stepResult := range pickleStepResults { - pickleStep := f.storage.MustGetPickleStep(stepResult.PickleStepID) + pickleStep := f.Storage.MustGetPickleStep(stepResult.PickleStepID) switch stepResult.Status { case passed: diff --git a/internal/formatters/fmt_pretty.go b/internal/formatters/fmt_pretty.go index ab0c3d0..0860dda 100644 --- a/internal/formatters/fmt_pretty.go +++ b/internal/formatters/fmt_pretty.go @@ -20,50 +20,52 @@ func init() { // PrettyFormatterFunc implements the FormatterFunc for the pretty formatter func PrettyFormatterFunc(suite string, out io.Writer) formatters.Formatter { - return &pretty{Basefmt: NewBaseFmt(suite, out)} + return &Pretty{Base: NewBase(suite, out)} } var outlinePlaceholderRegexp = regexp.MustCompile("<[^>]+>") -// a built in default pretty formatter -type pretty struct { - *Basefmt +// Pretty is a formatter for readable output. +type Pretty struct { + *Base firstFeature *bool } -func (f *pretty) TestRunStarted() { - f.Basefmt.TestRunStarted() +// TestRunStarted is triggered on test start. +func (f *Pretty) TestRunStarted() { + f.Base.TestRunStarted() - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() firstFeature := true f.firstFeature = &firstFeature } -func (f *pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) { - f.lock.Lock() +// Feature receives gherkin document. +func (f *Pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) { + f.Lock.Lock() if !*f.firstFeature { fmt.Fprintln(f.out, "") } *f.firstFeature = false - f.lock.Unlock() + f.Lock.Unlock() - f.Basefmt.Feature(gd, p, c) + f.Base.Feature(gd, p, c) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.printFeature(gd.Feature) } -// Pickle takes a gherkin node for formatting -func (f *pretty) Pickle(pickle *messages.Pickle) { - f.Basefmt.Pickle(pickle) +// Pickle takes a gherkin node for formatting. +func (f *Pretty) Pickle(pickle *messages.Pickle) { + f.Base.Pickle(pickle) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() if len(pickle.Steps) == 0 { f.printUndefinedPickle(pickle) @@ -71,52 +73,57 @@ func (f *pretty) Pickle(pickle *messages.Pickle) { } } -func (f *pretty) Passed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Passed(pickle, step, match) +// Passed captures passed step. +func (f *Pretty) Passed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Passed(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.printStep(pickle, step) } -func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Skipped(pickle, step, match) +// Skipped captures skipped step. +func (f *Pretty) Skipped(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Skipped(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.printStep(pickle, step) } -func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Undefined(pickle, step, match) +// Undefined captures undefined step. +func (f *Pretty) Undefined(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Undefined(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.printStep(pickle, step) } -func (f *pretty) Failed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) { - f.Basefmt.Failed(pickle, step, match, err) +// Failed captures failed step. +func (f *Pretty) Failed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) { + f.Base.Failed(pickle, step, match, err) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.printStep(pickle, step) } -func (f *pretty) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Pending(pickle, step, match) +// Pending captures pending step. +func (f *Pretty) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Pending(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.printStep(pickle, step) } -func (f *pretty) printFeature(feature *messages.Feature) { +func (f *Pretty) printFeature(feature *messages.Feature) { fmt.Fprintln(f.out, keywordAndName(feature.Keyword, feature.Name)) if strings.TrimSpace(feature.Description) != "" { for _, line := range strings.Split(feature.Description, "\n") { @@ -133,8 +140,8 @@ func keywordAndName(keyword, name string) string { return title } -func (f *pretty) scenarioLengths(pickle *messages.Pickle) (scenarioHeaderLength int, maxLength int) { - feature := f.storage.MustGetFeature(pickle.Uri) +func (f *Pretty) scenarioLengths(pickle *messages.Pickle) (scenarioHeaderLength int, maxLength int) { + feature := f.Storage.MustGetFeature(pickle.Uri) astScenario := feature.FindScenario(pickle.AstNodeIds[0]) astBackground := feature.FindBackground(pickle.AstNodeIds[0]) @@ -148,15 +155,15 @@ func (f *pretty) scenarioLengths(pickle *messages.Pickle) (scenarioHeaderLength return scenarioHeaderLength, maxLength } -func (f *pretty) printScenarioHeader(pickle *messages.Pickle, astScenario *messages.Scenario, spaceFilling int) { - feature := f.storage.MustGetFeature(pickle.Uri) +func (f *Pretty) printScenarioHeader(pickle *messages.Pickle, astScenario *messages.Scenario, spaceFilling int) { + feature := f.Storage.MustGetFeature(pickle.Uri) text := s(f.indent) + keywordAndName(astScenario.Keyword, astScenario.Name) text += s(spaceFilling) + line(feature.Uri, astScenario.Location) fmt.Fprintln(f.out, "\n"+text) } -func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) { - feature := f.storage.MustGetFeature(pickle.Uri) +func (f *Pretty) printUndefinedPickle(pickle *messages.Pickle) { + feature := f.Storage.MustGetFeature(pickle.Uri) astScenario := feature.FindScenario(pickle.AstNodeIds[0]) astBackground := feature.FindBackground(pickle.AstNodeIds[0]) @@ -197,18 +204,18 @@ func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) { } } -// Summary sumarize the feature formatter output -func (f *pretty) Summary() { - failedStepResults := f.storage.MustGetPickleStepResultsByStatus(failed) +// Summary renders summary information. +func (f *Pretty) Summary() { + failedStepResults := f.Storage.MustGetPickleStepResultsByStatus(failed) if len(failedStepResults) > 0 { fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n") sort.Sort(sortPickleStepResultsByPickleStepID(failedStepResults)) for _, fail := range failedStepResults { - pickle := f.storage.MustGetPickle(fail.PickleID) - pickleStep := f.storage.MustGetPickleStep(fail.PickleStepID) - feature := f.storage.MustGetFeature(pickle.Uri) + pickle := f.Storage.MustGetPickle(fail.PickleID) + pickleStep := f.Storage.MustGetPickleStep(fail.PickleStepID) + feature := f.Storage.MustGetFeature(pickle.Uri) astScenario := feature.FindScenario(pickle.AstNodeIds[0]) scenarioDesc := fmt.Sprintf("%s: %s", astScenario.Keyword, pickle.Name) @@ -222,14 +229,14 @@ func (f *pretty) Summary() { } } - f.Basefmt.Summary() + f.Base.Summary() } -func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps int) { +func (f *Pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps int) { var errorMsg string var clr = green - feature := f.storage.MustGetFeature(pickle.Uri) + feature := f.Storage.MustGetFeature(pickle.Uri) astScenario := feature.FindScenario(pickle.AstNodeIds[0]) scenarioHeaderLength, maxLength := f.scenarioLengths(pickle) @@ -237,7 +244,7 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in printExampleHeader := exampleTable.TableBody[0].Id == exampleRow.Id firstExamplesTable := astScenario.Examples[0].Location.Line == exampleTable.Location.Line - pickleStepResults := f.storage.MustGetPickleStepResultsByPickleID(pickle.Id) + pickleStepResults := f.Storage.MustGetPickleStepResultsByPickleID(pickle.Id) firstExecutedScenarioStep := len(pickleStepResults) == backgroundSteps+1 if firstExamplesTable && printExampleHeader && firstExecutedScenarioStep { @@ -270,7 +277,7 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in if firstExamplesTable && printExampleHeader { // in first example, we need to print steps - pickleStep := f.storage.MustGetPickleStep(result.PickleStepID) + pickleStep := f.Storage.MustGetPickleStep(result.PickleStepID) astStep := feature.FindStep(pickleStep.AstNodeIds[0]) var text = "" @@ -327,7 +334,7 @@ func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in } } -func (f *pretty) printTableRow(row *messages.TableRow, max []int, clr colors.ColorFunc) { +func (f *Pretty) printTableRow(row *messages.TableRow, max []int, clr colors.ColorFunc) { cells := make([]string, len(row.Cells)) for i, cell := range row.Cells { @@ -339,12 +346,12 @@ func (f *pretty) printTableRow(row *messages.TableRow, max []int, clr colors.Col fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |") } -func (f *pretty) printTableHeader(row *messages.TableRow, max []int) { +func (f *Pretty) printTableHeader(row *messages.TableRow, max []int) { f.printTableRow(row, max, cyan) } -func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleStep) { - feature := f.storage.MustGetFeature(pickle.Uri) +func (f *Pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleStep) { + feature := f.Storage.MustGetFeature(pickle.Uri) astBackground := feature.FindBackground(pickle.AstNodeIds[0]) astScenario := feature.FindScenario(pickle.AstNodeIds[0]) astStep := feature.FindStep(pickleStep.AstNodeIds[0]) @@ -387,7 +394,7 @@ func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleS f.printScenarioHeader(pickle, astScenario, maxLength-scenarioHeaderLength) } - pickleStepResult := f.storage.MustGetPickleStepResult(pickleStep.Id) + pickleStepResult := f.Storage.MustGetPickleStepResult(pickleStep.Id) text := s(f.indent*2) + pickleStepResult.Status.Color()(strings.TrimSpace(astStep.Keyword)) + " " + pickleStepResult.Status.Color()(pickleStep.Text) if pickleStepResult.Def != nil { text += s(maxLength - stepLength + 1) @@ -414,7 +421,7 @@ func (f *pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleS } } -func (f *pretty) printDocString(docString *messages.DocString) { +func (f *Pretty) printDocString(docString *messages.DocString) { var ct string if len(docString.MediaType) > 0 { @@ -432,7 +439,7 @@ func (f *pretty) printDocString(docString *messages.DocString) { // print table with aligned table cells // @TODO: need to make example header cells bold -func (f *pretty) printTable(t *messages.PickleTable, c colors.ColorFunc) { +func (f *Pretty) printTable(t *messages.PickleTable, c colors.ColorFunc) { maxColLengths := maxColLengths(t, c) var cols = make([]string, len(t.Rows[0].Cells)) @@ -512,7 +519,7 @@ func longestExampleRow(t *messages.Examples, clrs ...colors.ColorFunc) []int { return longest } -func (f *pretty) longestStep(steps []*messages.Step, pickleLength int) int { +func (f *Pretty) longestStep(steps []*messages.Step, pickleLength int) int { max := pickleLength for _, step := range steps { @@ -533,10 +540,10 @@ func line(path string, loc *messages.Location) string { return " " + blackb(fmt.Sprintf("# %s:%d", path, loc.Line)) } -func (f *pretty) lengthPickleStep(keyword, text string) int { +func (f *Pretty) lengthPickleStep(keyword, text string) int { return f.indent*2 + utf8.RuneCountInString(strings.TrimSpace(keyword)+" "+text) } -func (f *pretty) lengthPickle(keyword, name string) int { +func (f *Pretty) lengthPickle(keyword, name string) int { return f.indent + utf8.RuneCountInString(strings.TrimSpace(keyword)+": "+name) } diff --git a/internal/formatters/fmt_progress.go b/internal/formatters/fmt_progress.go index a869a02..86dbdf6 100644 --- a/internal/formatters/fmt_progress.go +++ b/internal/formatters/fmt_progress.go @@ -16,42 +16,49 @@ func init() { formatters.Format("progress", "Prints a character per step.", ProgressFormatterFunc) } -// ProgressFormatterFunc implements the FormatterFunc for the progress formatter +// ProgressFormatterFunc implements the FormatterFunc for the progress formatter. func ProgressFormatterFunc(suite string, out io.Writer) formatters.Formatter { + return NewProgress(suite, out) +} + +// NewProgress creates a new progress formatter. +func NewProgress(suite string, out io.Writer) *Progress { steps := 0 - return &progress{ - Basefmt: NewBaseFmt(suite, out), - stepsPerRow: 70, - steps: &steps, + return &Progress{ + Base: NewBase(suite, out), + StepsPerRow: 70, + Steps: &steps, } } -type progress struct { - *Basefmt - stepsPerRow int - steps *int +// Progress is a minimalistic formatter. +type Progress struct { + *Base + StepsPerRow int + Steps *int } -func (f *progress) Summary() { - left := math.Mod(float64(*f.steps), float64(f.stepsPerRow)) +// Summary renders summary information. +func (f *Progress) Summary() { + left := math.Mod(float64(*f.Steps), float64(f.StepsPerRow)) if left != 0 { - if *f.steps > f.stepsPerRow { - fmt.Fprintf(f.out, s(f.stepsPerRow-int(left))+fmt.Sprintf(" %d\n", *f.steps)) + if *f.Steps > f.StepsPerRow { + fmt.Fprintf(f.out, s(f.StepsPerRow-int(left))+fmt.Sprintf(" %d\n", *f.Steps)) } else { - fmt.Fprintf(f.out, " %d\n", *f.steps) + fmt.Fprintf(f.out, " %d\n", *f.Steps) } } var failedStepsOutput []string - failedSteps := f.storage.MustGetPickleStepResultsByStatus(failed) + failedSteps := f.Storage.MustGetPickleStepResultsByStatus(failed) sort.Sort(sortPickleStepResultsByPickleStepID(failedSteps)) for _, sr := range failedSteps { if sr.Status == failed { - pickle := f.storage.MustGetPickle(sr.PickleID) - pickleStep := f.storage.MustGetPickleStep(sr.PickleStepID) - feature := f.storage.MustGetFeature(pickle.Uri) + pickle := f.Storage.MustGetPickle(sr.PickleID) + pickleStep := f.Storage.MustGetPickleStep(sr.PickleStepID) + feature := f.Storage.MustGetFeature(pickle.Uri) sc := feature.FindScenario(pickle.AstNodeIds[0]) scenarioDesc := fmt.Sprintf("%s: %s", sc.Keyword, pickle.Name) @@ -77,11 +84,11 @@ func (f *progress) Summary() { } fmt.Fprintln(f.out, "") - f.Basefmt.Summary() + f.Base.Summary() } -func (f *progress) step(pickleStepID string) { - pickleStepResult := f.storage.MustGetPickleStepResult(pickleStepID) +func (f *Progress) step(pickleStepID string) { + pickleStepResult := f.Storage.MustGetPickleStepResult(pickleStepID) switch pickleStepResult.Status { case passed: @@ -96,54 +103,59 @@ func (f *progress) step(pickleStepID string) { fmt.Fprint(f.out, yellow("P")) } - *f.steps++ + *f.Steps++ - if math.Mod(float64(*f.steps), float64(f.stepsPerRow)) == 0 { - fmt.Fprintf(f.out, " %d\n", *f.steps) + if math.Mod(float64(*f.Steps), float64(f.StepsPerRow)) == 0 { + fmt.Fprintf(f.out, " %d\n", *f.Steps) } } -func (f *progress) Passed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Passed(pickle, step, match) +// Passed captures passed step. +func (f *Progress) Passed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Passed(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(step.Id) } -func (f *progress) Skipped(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Skipped(pickle, step, match) +// Skipped captures skipped step. +func (f *Progress) Skipped(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Skipped(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(step.Id) } -func (f *progress) Undefined(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Undefined(pickle, step, match) +// Undefined captures undefined step. +func (f *Progress) Undefined(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Undefined(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(step.Id) } -func (f *progress) Failed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) { - f.Basefmt.Failed(pickle, step, match, err) +// Failed captures failed step. +func (f *Progress) Failed(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) { + f.Base.Failed(pickle, step, match, err) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(step.Id) } -func (f *progress) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { - f.Basefmt.Pending(pickle, step, match) +// Pending captures pending step. +func (f *Progress) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) { + f.Base.Pending(pickle, step, match) - f.lock.Lock() - defer f.lock.Unlock() + f.Lock.Lock() + defer f.Lock.Unlock() f.step(step.Id) } diff --git a/release-notes/v0.12.0.md b/release-notes/v0.12.0.md index b25e3c3..6321ef2 100644 --- a/release-notes/v0.12.0.md +++ b/release-notes/v0.12.0.md @@ -14,6 +14,11 @@ Now `godog` is able to use multiple formatters simultaneously with comma-separat `--format pretty,junit:report.xml,cucumber:report.json` will write `pretty` format to stdout, `junit` to report.xml and `cucumber` to report.json. +### Extensible formatters + +Standard formatters are now exported with type aliases so that a custom formatter can be built on top of it. +Please check [an example](../_examples/custom-formatter). + ### Contextualized hooks Scenario and Step hooks are now passing context to allow custom state communication. Returned context should generally diff --git a/suite_context_test.go b/suite_context_test.go index 57b65bf..f904fa8 100644 --- a/suite_context_test.go +++ b/suite_context_test.go @@ -324,9 +324,9 @@ func (tc *godogFeaturesScenario) cleanupSnippet(snip string) string { } func (tc *godogFeaturesScenario) theUndefinedStepSnippetsShouldBe(body *DocString) error { - f, ok := tc.testedSuite.fmt.(*formatters.Basefmt) + f, ok := tc.testedSuite.fmt.(*formatters.Base) if !ok { - return fmt.Errorf("this step requires *formatters.Basefmt, but there is: %T", tc.testedSuite.fmt) + return fmt.Errorf("this step requires *formatters.Base, but there is: %T", tc.testedSuite.fmt) } actual := tc.cleanupSnippet(f.Snippets())