diff --git a/features/docs/formatters/summary_formatter.feature b/features/docs/formatters/summary_formatter.feature new file mode 100644 index 0000000000..f6ac1138ab --- /dev/null +++ b/features/docs/formatters/summary_formatter.feature @@ -0,0 +1,34 @@ +Feature: Spec formatter + + This formatter mimics the output from tools like RSpec or Mocha, giving an + overview of each feature and scenario but omitting the steps. + + Background: + Given the standard step definitions + + Scenario: A couple of scenarios + Given a file named "features/test.feature" with: + """ + Feature: Test + Scenario: Passing + Given this step passes + + Scenario: Failing + Given this step fails + """ + When I run `cucumber --format summary` + Then it should fail with exactly: + """ + Test + Passing ✓ + Failing ✗ + + Failing Scenarios: + cucumber features/test.feature:5 # Scenario: Failing + + 2 scenarios (1 failed, 1 passed) + 2 steps (1 failed, 1 passed) + 0m0.012s + + """ + diff --git a/lib/cucumber/cli/options.rb b/lib/cucumber/cli/options.rb index a41c615163..f1968de99f 100644 --- a/lib/cucumber/cli/options.rb +++ b/lib/cucumber/cli/options.rb @@ -23,7 +23,8 @@ class Options 'junit' => ['Cucumber::Formatter::Junit', 'Generates a report similar to Ant+JUnit.'], 'json' => ['Cucumber::Formatter::Json', 'Prints the feature as JSON'], 'json_pretty' => ['Cucumber::Formatter::JsonPretty', 'Prints the feature as prettified JSON'], - 'debug' => ['Cucumber::Formatter::Debug', 'For developing formatters - prints the calls made to the listeners.'] + 'debug' => ['Cucumber::Formatter::Debug', 'For developing formatters - prints the calls made to the listeners.'], + 'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios'] } max = BUILTIN_FORMATS.keys.map{|s| s.length}.max FORMAT_HELP_MSG = ["Use --format rerun --out rerun.txt to write out failing", diff --git a/lib/cucumber/formatter/console.rb b/lib/cucumber/formatter/console.rb index 240722808d..8d9c628e9b 100644 --- a/lib/cucumber/formatter/console.rb +++ b/lib/cucumber/formatter/console.rb @@ -1,6 +1,5 @@ require 'cucumber/formatter/ansicolor' require 'cucumber/formatter/duration' -require 'cucumber/formatter/summary' require 'cucumber/gherkin/i18n' module Cucumber @@ -29,7 +28,6 @@ module Formatter module Console extend ANSIColor include Duration - include Summary def format_step(keyword, step_match, status, source_indent) comment = if source_indent @@ -80,51 +78,23 @@ def print_element_messages(element_messages, status, kind) end end - def print_stats(features, options) - duration = features ? features.duration : nil - print_statistics(duration, options) - end - - def print_statistics(duration, options) - failures = collect_failing_scenarios(runtime) - if !failures.empty? - print_failing_scenarios(failures, options.custom_profiles, options[:source]) + def print_statistics(duration, config, counts, issues) + if issues.any? + @io.puts issues.to_s + @io.puts end - @io.puts scenario_summary(runtime) {|status_count, status| format_string(status_count, status)} - @io.puts step_summary(runtime) {|status_count, status| format_string(status_count, status)} - @io.puts(format_duration(duration)) if duration && options[:duration] + @io.puts counts.to_s + @io.puts(format_duration(duration)) if duration && config.duration? - if runtime.configuration.randomize? + if config.randomize? @io.puts - @io.puts "Randomized with seed #{runtime.configuration.seed}" + @io.puts "Randomized with seed #{config.seed}" end @io.flush end - def collect_failing_scenarios(runtime) - # TODO: brittle - stop coupling to types - scenario_class = LegacyApi::Ast::Scenario - example_table_class = Core::Ast::ExamplesTable - - runtime.scenarios(:failed).select do |s| - [scenario_class, example_table_class].include?(s.class) - end.map do |s| - s.is_a?(example_table_class)? s.scenario_outline : s - end - end - - def print_failing_scenarios(failures, custom_profiles, given_source) - @io.puts format_string("Failing Scenarios:", :failed) - failures.each do |failure| - profiles_string = custom_profiles.empty? ? '' : (custom_profiles.map{|profile| "-p #{profile}" }).join(' ') + ' ' - source = given_source ? format_string(" # " + failure.name, :comment) : '' - @io.puts format_string("cucumber #{profiles_string}" + failure.location, :failed) + source - end - @io.puts - end - def print_exception(e, status, indent) string = exception_message_string(e, indent) @io.puts(format_string(string, status)) diff --git a/lib/cucumber/formatter/console_counts.rb b/lib/cucumber/formatter/console_counts.rb new file mode 100644 index 0000000000..cd96da69dd --- /dev/null +++ b/lib/cucumber/formatter/console_counts.rb @@ -0,0 +1,57 @@ +require 'cucumber/formatter/console' + +module Cucumber + module Formatter + class ConsoleCounts + include Console + + def initialize(config) + @test_case_summary = Core::Test::Result::Summary.new + @test_step_summary = Core::Test::Result::Summary.new + + config.on_event :test_case_finished do |event| + event.result.describe_to @test_case_summary + end + + config.on_event :test_step_finished do |event| + event.result.describe_to @test_step_summary if from_gherkin?(event.test_step) + end + end + + def to_s + [ + [scenario_count, status_counts(@test_case_summary)].compact.join(" "), + [step_count, status_counts(@test_step_summary)].compact.join(" ") + ].join("\n") + end + + private + + def from_gherkin?(test_step) + test_step.source.last.location.file.match(/\.feature$/) + end + + def scenario_count + count = @test_case_summary.total + "#{count} scenario" + (count == 1 ? "" : "s") + end + + def step_count + count = @test_step_summary.total + "#{count} step" + (count == 1 ? "" : "s") + end + + def status_counts(summary) + counts = [:failed, :skipped, :undefined, :pending, :passed].map { |status| + count = summary.total(status) + [status, count] + }.select { |status, count| + count > 0 + }.map { |status, count| + format_string("#{count} #{status}", status) + } + "(#{counts.join(", ")})" if counts.any? + end + end + end +end diff --git a/lib/cucumber/formatter/console_issues.rb b/lib/cucumber/formatter/console_issues.rb new file mode 100644 index 0000000000..be8ef7ba2f --- /dev/null +++ b/lib/cucumber/formatter/console_issues.rb @@ -0,0 +1,37 @@ +require 'cucumber/formatter/console' + +module Cucumber + module Formatter + class ConsoleIssues + include Console + + def initialize(config) + @failures = [] + @config = config + @config.on_event(:test_case_finished) do |event| + @failures << event.test_case if event.result.failed? + end + end + + def to_s + return if @failures.empty? + result = [ format_string("Failing Scenarios:", :failed) ] + @failures.map { |failure| + source = @config.source? ? format_string(" # #{failure.keyword}: #{failure.name}", :comment) : '' + format_string("cucumber #{profiles_string}" + failure.location, :failed) + source + } + result.join("\n") + end + + def any? + @failures.any? + end + + private + + def profiles_string + return if @config.custom_profiles.empty? + @config.custom_profiles.map { |profile| "-p #{profile}" }.join(' ') + ' ' + end + end + end +end diff --git a/lib/cucumber/formatter/pretty.rb b/lib/cucumber/formatter/pretty.rb index af40ebed9b..1cfe1a81cd 100644 --- a/lib/cucumber/formatter/pretty.rb +++ b/lib/cucumber/formatter/pretty.rb @@ -2,6 +2,8 @@ require 'cucumber/formatter/console' require 'cucumber/formatter/io' require 'cucumber/gherkin/formatter/escaping' +require 'cucumber/formatter/console_counts' +require 'cucumber/formatter/console_issues' module Cucumber module Formatter @@ -22,12 +24,15 @@ class Pretty def initialize(runtime, path_or_io, options) @runtime, @io, @options = runtime, ensure_io(path_or_io), options + @config = runtime.configuration @exceptions = [] @indent = 0 @prefixes = options[:prefixes] || {} @delayed_messages = [] @previous_step_keyword = nil @snippets_input = [] + @counts = ConsoleCounts.new(runtime.configuration) + @issues = ConsoleIssues.new(runtime.configuration) end def before_features(features) @@ -238,7 +243,7 @@ def cell_prefix(status) end def print_summary(features) - print_stats(features, @options) + print_statistics(features.duration, @config, @counts, @issues) print_snippets(@options) print_passing_wip(@options) end diff --git a/lib/cucumber/formatter/progress.rb b/lib/cucumber/formatter/progress.rb index ac0763bf61..a8c75f768d 100644 --- a/lib/cucumber/formatter/progress.rb +++ b/lib/cucumber/formatter/progress.rb @@ -1,6 +1,8 @@ require 'cucumber/core/report/summary' require 'cucumber/formatter/backtrace_filter' require 'cucumber/formatter/console' +require 'cucumber/formatter/console_counts' +require 'cucumber/formatter/console_issues' require 'cucumber/formatter/io' require 'cucumber/formatter/duration_extractor' require 'cucumber/formatter/hook_query_visitor' @@ -25,6 +27,8 @@ def initialize(config) @failed_results = [] @failed_test_cases = [] @passed_test_cases = [] + @counts = ConsoleCounts.new(config) + @issues = ConsoleIssues.new(config) config.on_event :step_match, &method(:on_step_match) config.on_event :test_case_starting, &method(:on_test_case_starting) config.on_event :test_step_finished, &method(:on_test_step_finished) @@ -74,7 +78,7 @@ def on_test_run_finished(_event) def print_summary print_elements(@pending_step_matches, :pending, 'steps') print_elements(@failed_results, :failed, 'steps') - print_statistics_local(@total_duration) + print_statistics(@total_duration, @config, @counts, @issues) snippet_text_proc = lambda { |step_keyword, step_name, multiline_arg| snippet_text(step_keyword, step_name, multiline_arg) } @@ -87,28 +91,6 @@ def print_summary end end - def print_statistics_local(duration) - if @failed_test_cases.any? - failed_test_cases_data = @failed_test_cases.map do |test_case| - TestCaseData.new(name="#{test_case.keyword}: #{test_case.name}", location=test_case.location) - end - print_failing_scenarios(failed_test_cases_data, config.custom_profiles, config.source?) - end - - scenarios_proc = lambda{|status| summary.test_cases.total(status)} - @io.puts dump_summary_counts(summary.test_cases.total, scenarios_proc, "scenario") {|status_count, status| format_string(status_count, status)} - steps_proc = lambda{|status| summary.test_steps.total(status)} - @io.puts dump_summary_counts(summary.test_steps.total, steps_proc, "step") {|status_count, status| format_string(status_count, status)} - @io.puts(format_duration(duration)) if duration && config.duration? - - if config.randomize? - @io.puts - @io.puts "Randomized with seed #{config.seed}" - end - - @io.flush - end - CHARS = { :passed => '.', :failed => 'F', diff --git a/lib/cucumber/formatter/summary.rb b/lib/cucumber/formatter/summary.rb index 33d633d78b..4b531cc11f 100644 --- a/lib/cucumber/formatter/summary.rb +++ b/lib/cucumber/formatter/summary.rb @@ -1,48 +1,57 @@ +require 'cucumber/formatter/io' +require 'cucumber/formatter/console' +require 'cucumber/formatter/console_counts' +require 'cucumber/formatter/console_issues' +require 'cucumber/core/test/result' + module Cucumber module Formatter - module Summary - def scenario_summary(runtime, &block) - scenarios_proc = lambda{|status| elements = runtime.scenarios(status)} - scenario_count_proc = element_count_proc(scenarios_proc) - dump_summary_counts(runtime.scenarios.length, scenario_count_proc, "scenario", &block) - end + # Summary formatter, outputting only feature / scenario titles + class Summary + include Io + include Console - def step_summary(runtime, &block) - steps_proc = lambda{|status| runtime.steps(status)} - step_count_proc = element_count_proc(steps_proc) - dump_summary_counts(runtime.steps.length, step_count_proc, "step", &block) - end + def initialize(config) + @config, @io = config, ensure_io(config.out_stream) + @counts = ConsoleCounts.new(@config) + @issues = ConsoleIssues.new(@config) + @start_time = Time.now - def dump_summary_counts(total_count, status_proc, what, &block) - dump_count(total_count, what) + dump_status_counts(status_proc, &block) + @config.on_event :test_case_starting do |event| + print_feature event.test_case + print_test_case event.test_case + end + + @config.on_event :test_case_finished do |event| + print_result event.result + end + + @config.on_event :test_run_finished do |event| + duration = Time.now - @start_time + @io.puts + print_statistics(duration, @config, @counts, @issues) + end end private - def element_count_proc(find_elements_proc) - lambda { |status| - elements = find_elements_proc.call(status) - elements.any? ? elements.length : 0 - } + def print_feature(test_case) + feature = test_case.feature + return if @current_feature == feature + @io.puts unless @current_feature.nil? + @io.puts feature + @current_feature = feature end - def dump_status_counts(element_count_proc) - counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status| - count = element_count_proc.call(status) - count != 0 ? yield("#{count} #{status.to_s}", status) : nil - end.compact - if counts.any? - " (#{counts.join(', ')})" - else - "" - end + def print_test_case(test_case) + @io.print " #{test_case.name} " end - def dump_count(count, what, state=nil) - [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(" ") + def print_result(result) + @io.puts format_string(result, result.to_sym) end - end end end + diff --git a/lib/cucumber/multiline_argument/data_table.rb b/lib/cucumber/multiline_argument/data_table.rb index 27bb48b5ef..c4dca903b2 100644 --- a/lib/cucumber/multiline_argument/data_table.rb +++ b/lib/cucumber/multiline_argument/data_table.rb @@ -469,7 +469,8 @@ def to_s(options = {}) #:nodoc: c = Cucumber::Term::ANSIColor.coloring? Cucumber::Term::ANSIColor.coloring = options[:color] - formatter = Formatter::Pretty.new(nil, io, options) + runtime = Struct.new(:configuration).new(Configuration.new) + formatter = Formatter::Pretty.new(runtime, io, options) formatter.instance_variable_set('@indent', options[:indent]) Formatter::LegacyApi::Ast::MultilineArg.for(self).accept(Formatter::Fanout.new([formatter])) Cucumber::Term::ANSIColor.coloring = c diff --git a/spec/cucumber/formatter/console_counts_spec.rb b/spec/cucumber/formatter/console_counts_spec.rb new file mode 100644 index 0000000000..01029be277 --- /dev/null +++ b/spec/cucumber/formatter/console_counts_spec.rb @@ -0,0 +1,14 @@ +require 'cucumber/configuration' +require 'cucumber/formatter/console_counts' + +module Cucumber + module Formatter + describe ConsoleCounts do + it "works for zero" do + config = Configuration.new + counts = ConsoleCounts.new(config) + expect(counts.to_s).to eq "0 scenarios\n0 steps" + end + end + end +end