diff --git a/internal/formatters/fmt_multi.go b/internal/formatters/fmt_multi.go new file mode 100644 index 0000000..a54c80a --- /dev/null +++ b/internal/formatters/fmt_multi.go @@ -0,0 +1,133 @@ +package formatters + +import ( + "io" + + "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/storage" + "github.com/cucumber/messages-go/v10" +) + +// MultiFormatter passes test progress to multiple formatters. +type MultiFormatter struct { + formatters []formatter + repeater repeater +} + +type formatter struct { + fmt formatters.FormatterFunc + out io.Writer + close bool +} + +type repeater []formatters.Formatter + +type storageFormatter interface { + SetStorage(s *storage.Storage) +} + +// SetStorage passes storage to all added formatters. +func (r repeater) SetStorage(s *storage.Storage) { + for _, f := range r { + if ss, ok := f.(storageFormatter); ok { + ss.SetStorage(s) + } + } +} + +// TestRunStarted triggers TestRunStarted for all added formatters. +func (r repeater) TestRunStarted() { + for _, f := range r { + f.TestRunStarted() + } +} + +// Feature triggers Feature for all added formatters. +func (r repeater) Feature(document *messages.GherkinDocument, s string, bytes []byte) { + for _, f := range r { + f.Feature(document, s, bytes) + } +} + +// Pickle triggers Pickle for all added formatters. +func (r repeater) Pickle(pickle *messages.Pickle) { + for _, f := range r { + f.Pickle(pickle) + } +} + +// Defined triggers Defined for all added formatters. +func (r repeater) Defined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, definition *formatters.StepDefinition) { + for _, f := range r { + f.Defined(pickle, step, definition) + } +} + +// Failed triggers Failed for all added formatters. +func (r repeater) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, definition *formatters.StepDefinition, err error) { + for _, f := range r { + f.Failed(pickle, step, definition, err) + } +} + +// Passed triggers Passed for all added formatters. +func (r repeater) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, definition *formatters.StepDefinition) { + for _, f := range r { + f.Passed(pickle, step, definition) + } +} + +// Skipped triggers Skipped for all added formatters. +func (r repeater) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, definition *formatters.StepDefinition) { + for _, f := range r { + f.Skipped(pickle, step, definition) + } +} + +// Undefined triggers Undefined for all added formatters. +func (r repeater) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, definition *formatters.StepDefinition) { + for _, f := range r { + f.Undefined(pickle, step, definition) + } +} + +// Pending triggers Pending for all added formatters. +func (r repeater) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, definition *formatters.StepDefinition) { + for _, f := range r { + f.Pending(pickle, step, definition) + } +} + +// Summary triggers Summary for all added formatters. +func (r repeater) Summary() { + for _, f := range r { + f.Summary() + } +} + +// Add adds formatter with output writer. +func (m *MultiFormatter) Add(name string, out io.Writer) { + f := formatters.FindFmt(name) + if f == nil { + panic("formatter not found: " + name) + } + + m.formatters = append(m.formatters, formatter{ + fmt: f, + out: out, + }) +} + +// FormatterFunc implements the FormatterFunc for the multi formatter. +func (m *MultiFormatter) FormatterFunc(suite string, out io.Writer) formatters.Formatter { + for _, f := range m.formatters { + out := out + if f.out != nil { + out = f.out + } + + m.repeater = append(m.repeater, f.fmt(suite, out)) + } + + return m.repeater +} diff --git a/run.go b/run.go index 0a82200..1a064ab 100644 --- a/run.go +++ b/run.go @@ -16,6 +16,7 @@ import ( "github.com/cucumber/godog/colors" "github.com/cucumber/godog/formatters" + ifmt "github.com/cucumber/godog/internal/formatters" "github.com/cucumber/godog/internal/models" "github.com/cucumber/godog/internal/parser" "github.com/cucumber/godog/internal/storage" @@ -141,28 +142,49 @@ func runWithOptions(suiteName string, runner runner, opt Options) int { output = opt.Output } - if formatterParts := strings.SplitN(opt.Format, ":", 2); len(formatterParts) > 1 { - f, err := os.Create(formatterParts[1]) - if err != nil { - err = fmt.Errorf( - `couldn't create file with name: "%s", error: %s`, - formatterParts[1], err.Error(), - ) - fmt.Fprintln(os.Stderr, err) + multiFmt := ifmt.MultiFormatter{} + for _, formatter := range strings.Split(opt.Format, ",") { + out := output + formatterParts := strings.SplitN(formatter, ":", 2) + + if len(formatterParts) > 1 { + f, err := os.Create(formatterParts[1]) + if err != nil { + err = fmt.Errorf( + `couldn't create file with name: "%s", error: %s`, + formatterParts[1], err.Error(), + ) + fmt.Fprintln(os.Stderr, err) + + return exitOptionError + } + + defer f.Close() + + out = f + } + + if opt.NoColors { + out = colors.Uncolored(out) + } else { + out = colors.Colored(out) + } + + if nil == formatters.FindFmt(formatterParts[0]) { + var names []string + for name := range formatters.AvailableFormatters() { + names = append(names, name) + } + fmt.Fprintln(os.Stderr, fmt.Errorf( + `unregistered formatter name: "%s", use one of: %s`, + opt.Format, + strings.Join(names, ", "), + )) return exitOptionError } - defer f.Close() - - output = f - opt.Format = formatterParts[0] - } - - if opt.NoColors { - output = colors.Uncolored(output) - } else { - output = colors.Colored(output) + multiFmt.Add(formatterParts[0], out) } if opt.ShowStepDefinitions { @@ -184,20 +206,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int { opt.Concurrency = 1 } - formatter := formatters.FindFmt(opt.Format) - if nil == formatter { - var names []string - for name := range formatters.AvailableFormatters() { - names = append(names, name) - } - fmt.Fprintln(os.Stderr, fmt.Errorf( - `unregistered formatter name: "%s", use one of: %s`, - opt.Format, - strings.Join(names, ", "), - )) - return exitOptionError - } - runner.fmt = formatter(suiteName, output) + runner.fmt = multiFmt.FormatterFunc(suiteName, output) var err error if runner.features, err = parser.ParseFeatures(opt.Tags, opt.Paths); err != nil {