Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

generalize as a JSON formatter

  • Loading branch information...
commit f6c460120e007be757c3a6dfdfd3dfb7b149fa5d 1 parent 9901cf7
Jesse Newland authored
63 features/formatter.feature
... ... @@ -1,6 +1,7 @@
1   -Feature: Scout formatter
2   - As a Scout user
3   - I want to receive reports of failing cucumber features
  1 +Feature: JSON formatter
  2 + As a developer
  3 + I want to receive reports of failing cucumber features in a parsable format
  4 + In order to facilitace elegant continuous integration
4 5 In order to protect revenue
5 6
6 7 Background:
@@ -63,16 +64,52 @@ Feature: Scout formatter
63 64 """
64 65
65 66 Scenario: One Failing Feature
66   - When I run cucumber -r ../../../lib -r features/step_definitions -f Cucumber::Formatter::Scout features/one_failure.feature
67   - Then the output should contain "output[:status_counts][:failed]" set to "1"
68   - And the output should contain "output[:status_counts][:passed]" set to "1"
69   - And the output should contain "output[:status_counts][:undefined]" set to "1"
70   - And the output should contain "output[:status_counts][:pending]" set to "1"
  67 + When I run cucumber -r ../../../lib -r features/step_definitions -f Cucumber::Formatter::JSON features/one_failure.feature
  68 + Then the output should contain "output['status_counts']['failed']" set to "1"
  69 + And the output should contain "output['status_counts']['passed']" set to "1"
  70 + And the output should contain "output['status_counts']['undefined']" set to "1"
  71 + And the output should contain "output['status_counts']['pending']" set to "1"
  72 + And the output should contain the failing feature
  73 + """
  74 + Scenario:: Failing
  75 + Given failing # features/step_definitions/steps.rb:1
  76 + FAIL (RuntimeError)
  77 + ./features/step_definitions/steps.rb:2:in `/failing/'
  78 + features/one_failure.feature:4:in `Given failing'
  79 +
  80 + """
71 81 Scenario: Multiple Failing Features
72   - When I run cucumber -r ../../../lib -r features/step_definitions -f Cucumber::Formatter::Scout features/multiple_failures.feature
73   - Then the output should contain "output[:status_counts][:failed]" set to "3"
74   - And the output should contain "output[:status_counts][:passed]" set to "1"
  82 + When I run cucumber -r ../../../lib -r features/step_definitions -f Cucumber::Formatter::JSON features/multiple_failures.feature
  83 + Then the output should contain "output['status_counts']['failed']" set to "3"
  84 + And the output should contain "output['status_counts']['passed']" set to "1"
  85 + And the output should contain the failing feature
  86 + """
  87 + Scenario:: Failing
  88 + Given failing # features/step_definitions/steps.rb:1
  89 + FAIL (RuntimeError)
  90 + ./features/step_definitions/steps.rb:2:in `/failing/'
  91 + features/multiple_failures.feature:4:in `Given failing'
75 92
  93 + """
  94 + And the output should contain the failing feature
  95 + """
  96 + Scenario:: Failing2
  97 + Given failing # features/step_definitions/steps.rb:1
  98 + FAIL (RuntimeError)
  99 + ./features/step_definitions/steps.rb:2:in `/failing/'
  100 + features/multiple_failures.feature:7:in `Given failing'
  101 +
  102 + """
  103 + And the output should contain the failing feature
  104 + """
  105 + Scenario:: Failing3
  106 + Given failing # features/step_definitions/steps.rb:1
  107 + FAIL (RuntimeError)
  108 + ./features/step_definitions/steps.rb:2:in `/failing/'
  109 + features/multiple_failures.feature:10:in `Given failing'
  110 +
  111 + """
76 112 Scenario: All Features Passing
77   - When I run cucumber -r ../../../lib -r features/step_definitions -f Cucumber::Formatter::Scout features/all_passing.feature
78   - Then the output should contain "output[:status_counts][:passed]" set to "2"
  113 + When I run cucumber -r ../../../lib -r features/step_definitions -f Cucumber::Formatter::JSON features/all_passing.feature
  114 + Then the output should contain "output['status_counts']['passed']" set to "2"
  115 + And the output should contain no failing features
18 features/step_definitions/custom_steps.rb
... ... @@ -1,10 +1,18 @@
1   -require 'yaml'
  1 +require 'json'
2 2 Then /^the output should contain "([^"]*)" set to "([^"]*)"$/ do |variable, value|
3   - output = YAML.load(last_stdout)
  3 + Then 'STDERR should be empty'
  4 + output = JSON.parse(last_stdout)
4 5 eval(variable).to_s.should == value
5 6 end
6 7
7   -Then /^the output should contain the alert$/ do |success, output|
8   - output = YAML.load(last_stdout)
9   - output[:alerts].should include(output)
  8 +Then /^the output should contain the failing feature$/ do |alert|
  9 + Then 'STDERR should be empty'
  10 + output = JSON.parse(last_stdout)
  11 + output['failing_features'].should include(alert)
  12 +end
  13 +
  14 +Then /^the output should contain no failing features$/ do
  15 + Then 'STDERR should be empty'
  16 + output = JSON.parse(last_stdout)
  17 + output['failing_features'].should == []
10 18 end
244 lib/cucumber/formatter/json.rb
... ... @@ -0,0 +1,244 @@
  1 +require 'json'
  2 +require 'cucumber/formatter/io'
  3 +
  4 +module Cucumber
  5 + module Formatter
  6 + class JSON
  7 + include Io
  8 +
  9 + FORMATS = Hash.new{|hash, format| hash[format] = method(format).to_proc}
  10 +
  11 + def initialize(step_mother, path_or_io, options)
  12 + @io = ensure_io(path_or_io, "json")
  13 + @options = options
  14 + @status_counts = Hash.new{|h,k| h[k] = 0}
  15 + @indent = 0
  16 + @hash = {}
  17 + end
  18 +
  19 + def before_examples(*args)
  20 + @header_row = true
  21 + end
  22 +
  23 + def before_feature(feature)
  24 + @exceptions = []
  25 + @indent = 0
  26 + @feature = ''
  27 + end
  28 +
  29 + def feature_name(keyword, name = nil)
  30 + @feature << "#{keyword}: #{name}\n"
  31 + end
  32 +
  33 + def before_feature_element(feature_element)
  34 + @indent = 2
  35 + @element_exceptions = []
  36 + @feature_element = ""
  37 + @scenario_indent = 2
  38 + end
  39 +
  40 + def before_background(background)
  41 + @indent = 2
  42 + @scenario_indent = 2
  43 + @in_background = true
  44 + end
  45 +
  46 + def after_background(background)
  47 + @in_background = nil
  48 + end
  49 +
  50 + def background_name(keyword, name, file_colon_line, source_indent)
  51 + print_feature_element_name(keyword, name, file_colon_line, source_indent)
  52 + end
  53 +
  54 + def background_name(keyword, name, file_colon_line, source_indent)
  55 + print_feature_element_name(keyword, name, file_colon_line, source_indent)
  56 + end
  57 +
  58 + def before_examples_array(examples_array)
  59 + @indent = 4
  60 + @visiting_first_example_name = true
  61 + end
  62 +
  63 + def examples_name(keyword, name)
  64 + @feature_element << "\n" unless @visiting_first_example_name
  65 + @visiting_first_example_name = false
  66 + names = name.strip.empty? ? [name.strip] : name.split("\n")
  67 + @feature_element << " #{keyword}: #{names[0]}\n"
  68 + names[1..-1].each {|s| @feature_element << " #{s}\n" } unless names.empty?
  69 + @indent = 6
  70 + @scenario_indent = 6
  71 + end
  72 +
  73 + def before_outline_table(outline_table)
  74 + @table = outline_table
  75 + end
  76 +
  77 + def after_outline_table(outline_table)
  78 + @table = nil
  79 + @indent = 4
  80 + end
  81 +
  82 + def scenario_name(keyword, name, file_colon_line, source_indent)
  83 + print_feature_element_name(keyword, name, file_colon_line, source_indent)
  84 + end
  85 +
  86 + def before_step(step)
  87 + @current_step = step
  88 + @indent = 6
  89 + end
  90 +
  91 + def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
  92 + @hide_this_step = false
  93 + if exception
  94 + if @exceptions.include?(exception)
  95 + @hide_this_step = true
  96 + return
  97 + end
  98 + @element_exceptions << exception
  99 + @exceptions << exception
  100 + end
  101 + if status != :failed && @in_background ^ background
  102 + @hide_this_step = true
  103 + return
  104 + end
  105 + @status = status
  106 + end
  107 +
  108 + def step_name(keyword, step_match, status, source_indent, background)
  109 + return if @hide_this_step
  110 + source_indent = nil unless @options[:source]
  111 + name_to_report = format_step(keyword, step_match, status, source_indent)
  112 + @feature_element << name_to_report.indent(@scenario_indent + 2)
  113 + @feature_element << "\n"
  114 + end
  115 +
  116 + def py_string(string)
  117 + return if @hide_this_step
  118 + s = %{"""\n#{string}\n"""}.indent(@indent)
  119 + s = s.split("\n").map{|l| l =~ /^\s+$/ ? '' : l}.join("\n")
  120 + @feature_element << format_string(s, @current_step.status)
  121 + @feature_element << "\n"
  122 + end
  123 +
  124 + def exception(exception, status)
  125 + return if @hide_this_step
  126 + print_exception(exception, status, @indent)
  127 + end
  128 +
  129 + def before_multiline_arg(multiline_arg)
  130 + return if @options[:no_multiline] || @hide_this_step
  131 + @table = multiline_arg
  132 + end
  133 +
  134 + def after_multiline_arg(multiline_arg)
  135 + @table = nil
  136 + end
  137 +
  138 + def before_table_row(table_row)
  139 + return if !@table || @hide_this_step
  140 + @col_index = 0
  141 + @feature_element << ' |'.indent(@indent-2)
  142 + end
  143 +
  144 + def after_table_cell(cell)
  145 + return unless @table
  146 + @col_index += 1
  147 + end
  148 +
  149 + def table_cell_value(value, status)
  150 + return if !@table || @hide_this_step
  151 + status ||= @status || :passed
  152 + width = @table.col_width(@col_index)
  153 + cell_text = value.to_s || ''
  154 + padded = cell_text + (' ' * (width - cell_text.jlength))
  155 + prefix = cell_prefix(status)
  156 + @feature_element << ' ' + format_string("#{prefix}#{padded}", status) + ::Term::ANSIColor.reset(" |")
  157 + @feature_element << "\n"
  158 + end
  159 +
  160 + # mine
  161 +
  162 + def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
  163 + record_result(status, :step_match => step_match)
  164 + end
  165 +
  166 + def after_table_row(table_row)
  167 + unless @header_row
  168 + record_result(table_row.status) if table_row.respond_to?(:status)
  169 + end
  170 + @header_row = false if @header_row
  171 + return if !@table || @hide_this_step
  172 + print_table_row_announcements
  173 + @feature_element << "\n"
  174 + if table_row.exception && !@exceptions.include?(table_row.exception)
  175 + print_exception(table_row.exception, table_row.status, @indent)
  176 + end
  177 + end
  178 +
  179 + def after_features(steps)
  180 + @hash[:status_counts] = @status_counts
  181 + @io.print(@hash.to_json)
  182 + @io.flush
  183 + @io.close
  184 + end
  185 +
  186 + def after_feature_element(element)
  187 + @hash[:feature_elements] ||= []
  188 + @hash[:feature_elements] << @feature_element
  189 + @hash[:failing_features] ||= []
  190 + if @element_exceptions.size > 0
  191 + @hash[:failing_features] << @feature_element
  192 + end
  193 + @feature_element = ''
  194 + end
  195 +
  196 + private
  197 +
  198 + def record_result(status, opts={})
  199 + step_match = opts[:step_match] || true
  200 + @status_counts[status] = @status_counts[status] + 1
  201 + end
  202 +
  203 + def print_feature_element_name(keyword, name, file_colon_line, source_indent)
  204 + @feature_element << "\n" if @scenario_indent == 6
  205 + names = name.empty? ? [name] : name.split("\n")
  206 + line = "#{keyword}: #{names[0]}".indent(@scenario_indent)
  207 + @feature_element << line
  208 + @feature_element << "\n"
  209 + names[1..-1].each {|s| @feature_element << " #{s}\n"}
  210 + end
  211 +
  212 + def print_exception(e, status, indent)
  213 + @feature_element << format_string("#{e.message} (#{e.class})\n#{e.backtrace.join("\n")}".indent(indent), status)
  214 + @feature_element << "\n"
  215 + end
  216 +
  217 + def cell_prefix(status)
  218 + @prefixes[status]
  219 + end
  220 +
  221 + def format_string(string, status)
  222 + string
  223 + end
  224 +
  225 + def format_step(keyword, step_match, status, source_indent)
  226 + comment = if source_indent
  227 + c = (' # ' + step_match.file_colon_line).indent(source_indent)
  228 + format_string(c, :comment)
  229 + else
  230 + ''
  231 + end
  232 +
  233 + if status == :passed
  234 + line = keyword + ' ' + step_match.format_args(nil)
  235 + format_string(line, status)
  236 + else
  237 + line = keyword + ' ' + step_match.format_args(nil) + comment
  238 + format_string(line, status)
  239 + end
  240 + end
  241 +
  242 + end
  243 + end
  244 +end
47 lib/cucumber/formatter/scout.rb
... ... @@ -1,47 +0,0 @@
1   -require 'yaml'
2   -require 'cucumber/formatter/io'
3   -
4   -module Cucumber
5   - module Formatter
6   - class Scout
7   - include Io
8   -
9   - def initialize(step_mother, path_or_io, options)
10   - @io = ensure_io(path_or_io, "scout")
11   - @options = options
12   - @status_counts = Hash.new{|h,k| h[k] = 0}
13   - @hash = {}
14   - end
15   -
16   - def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
17   - record_result(status, :step_match => step_match)
18   - end
19   -
20   - def before_examples(*args)
21   - @header_row = true
22   - end
23   -
24   - def after_table_row(table_row)
25   - unless @header_row
26   - record_result(table_row.status) if table_row.respond_to?(:status)
27   - end
28   - @header_row = false if @header_row
29   - end
30   -
31   - def after_features(steps)
32   - @hash[:status_counts] = @status_counts
33   - @io.print(@hash.to_yaml)
34   - @io.flush
35   - @io.close
36   - end
37   -
38   - private
39   -
40   - def record_result(status, opts={})
41   - step_match = opts[:step_match] || true
42   - @status_counts[status] = @status_counts[status] + 1
43   - end
44   -
45   - end
46   - end
47   -end

0 comments on commit f6c4601

Please sign in to comment.
Something went wrong with that request. Please try again.