lib/cucumber/formatter/json.rb in cucumber-3.2.0 vs lib/cucumber/formatter/json.rb in cucumber-4.0.0.rc.1

- old
+ new

@@ -2,20 +2,21 @@ require 'multi_json' require 'base64' require 'cucumber/formatter/backtrace_filter' require 'cucumber/formatter/io' -require 'cucumber/formatter/hook_query_visitor' +require 'cucumber/formatter/ast_lookup' module Cucumber module Formatter # The formatter used for <tt>--format json</tt> class Json include Io def initialize(config) @io = ensure_io(config.out_stream) + @ast_lookup = AstLookup.new(config) @feature_hashes = [] @step_or_hook_hash = {} config.on_event :test_case_started, &method(:on_test_case_started) config.on_event :test_case_finished, &method(:on_test_case_finished) config.on_event :test_step_started, &method(:on_test_step_started) @@ -23,40 +24,42 @@ config.on_event :test_run_finished, &method(:on_test_run_finished) end def on_test_case_started(event) test_case = event.test_case - builder = Builder.new(test_case) - unless same_feature_as_previous_test_case?(test_case.feature) + builder = Builder.new(test_case, @ast_lookup) + unless same_feature_as_previous_test_case?(test_case) @feature_hash = builder.feature_hash @feature_hashes << @feature_hash end @test_case_hash = builder.test_case_hash if builder.background? + @in_background = true feature_elements << builder.background_hash @element_hash = builder.background_hash else + @in_background = false feature_elements << @test_case_hash @element_hash = @test_case_hash end @any_step_failed = false end def on_test_step_started(event) test_step = event.test_step return if internal_hook?(test_step) - hook_query = HookQueryVisitor.new(test_step) - if hook_query.hook? + if test_step.hook? @step_or_hook_hash = {} - hooks_of_type(hook_query) << @step_or_hook_hash + hooks_of_type(test_step) << @step_or_hook_hash return end if first_step_after_background?(test_step) + @in_background = false feature_elements << @test_case_hash @element_hash = @test_case_hash end - @step_or_hook_hash = create_step_hash(test_step.source.last) + @step_or_hook_hash = create_step_hash(test_step) steps << @step_or_hook_hash @step_hash = @step_or_hook_hash end def on_test_step_finished(event) @@ -80,66 +83,58 @@ def puts(message) test_step_output << message end def embed(src, mime_type, _label) - begin - is_file = File.file?(src) - rescue ArgumentError - is_file = false - end - - if is_file + if File.file?(src) content = File.open(src, 'rb', &:read) data = encode64(content) + elsif mime_type =~ /;base64$/ + mime_type = mime_type[0..-8] + data = src else - if mime_type =~ /;base64$/ - mime_type = mime_type[0..-8] - data = src - else - data = encode64(src) - end + data = encode64(src) end test_step_embeddings << { mime_type: mime_type, data: data } end private - def same_feature_as_previous_test_case?(feature) - current_feature[:uri] == feature.file && current_feature[:line] == feature.location.line + def same_feature_as_previous_test_case?(test_case) + current_feature[:uri] == test_case.location.file end def first_step_after_background?(test_step) - test_step.source[1].to_s != @element_hash[:name] + @in_background && test_step.location.lines.max >= @test_case_hash[:line] end def internal_hook?(test_step) - test_step.source.last.location.file.include?('lib/cucumber/') + test_step.location.file.include?('lib/cucumber/') end def current_feature - @feature_hash ||= {} + @feature_hash ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName end def feature_elements @feature_hash[:elements] ||= [] end def steps @element_hash[:steps] ||= [] end - def hooks_of_type(hook_query) - case hook_query.type - when :before - return before_hooks - when :after - return after_hooks - when :after_step - return after_step_hooks + def hooks_of_type(hook_step) + case hook_step.text + when 'Before hook' + before_hooks + when 'After hook' + after_hooks + when 'AfterStep hook' + after_step_hooks else - fail 'Unknown hook type ' + hook_query.type.to_s + raise 'Unknown hook type ' + hook_step.to_s end end def before_hooks @element_hash[:before] ||= [] @@ -163,34 +158,34 @@ def test_step_embeddings @step_or_hook_hash[:embeddings] ||= [] end - def create_step_hash(step_source) + def create_step_hash(test_step) + step_source = @ast_lookup.step_source(test_step).step step_hash = { - keyword: step_source.keyword, - name: step_source.to_s, - line: step_source.original_location.line + keyword: step_source[:keyword], + name: test_step.text, + line: test_step.location.lines.min } - step_hash[:comments] = Formatter.create_comments_array(step_source.comments) unless step_source.comments.empty? - step_hash[:doc_string] = create_doc_string_hash(step_source.multiline_arg) if step_source.multiline_arg.doc_string? - step_hash[:rows] = create_data_table_value(step_source.multiline_arg) if step_source.multiline_arg.data_table? + step_hash[:doc_string] = create_doc_string_hash(step_source[:doc_string]) unless step_source[:doc_string].nil? + step_hash[:rows] = create_data_table_value(step_source[:data_table]) unless step_source[:data_table].nil? step_hash end def create_doc_string_hash(doc_string) - content_type = doc_string.content_type ? doc_string.content_type : '' + content_type = doc_string[:content_type] || '' { - value: doc_string.content, + value: doc_string[:content], content_type: content_type, - line: doc_string.location.line + line: doc_string[:location][:line] } end def create_data_table_value(data_table) - data_table.raw.map do |row| - { cells: row } + data_table[:rows].map do |row| + { cells: row[:cells].map { |cell| cell[:value] } } end end def add_match_and_result(test_step, result) @step_or_hook_hash[:match] = create_match_hash(test_step, result) @@ -230,115 +225,96 @@ end class Builder attr_reader :feature_hash, :background_hash, :test_case_hash - def initialize(test_case) + def initialize(test_case, ast_lookup) @background_hash = nil - test_case.describe_source_to(self) - test_case.feature.background.describe_to(self) + uri = test_case.location.file + feature = ast_lookup.gherkin_document(uri)[:feature] + feature(feature, uri) + background(feature[:children].first[:background]) unless feature[:children].first[:background].nil? + scenario(ast_lookup.scenario_source(test_case), test_case) end def background? @background_hash != nil end - def feature(feature) + def feature(feature, uri) @feature_hash = { - uri: feature.file, - id: create_id(feature), - keyword: feature.keyword, - name: feature.to_s, - description: feature.description, - line: feature.location.line + id: create_id(feature[:name]), + uri: uri, + keyword: feature[:keyword], + name: feature[:name], + description: value_or_empty_string(feature[:description]), + line: feature[:location][:line] } - unless feature.tags.empty? - @feature_hash[:tags] = create_tags_array(feature.tags) - @test_case_hash[:tags] = if @test_case_hash[:tags] - @feature_hash[:tags] + @test_case_hash[:tags] - else - @feature_hash[:tags] - end - end - @feature_hash[:comments] = Formatter.create_comments_array(feature.comments) unless feature.comments.empty? - @test_case_hash[:id].insert(0, @feature_hash[:id] + ';') + return if feature[:tags].empty? + @feature_hash[:tags] = create_tags_array_from_hash_array(feature[:tags]) end def background(background) @background_hash = { - keyword: background.keyword, - name: background.to_s, - description: background.description, - line: background.location.line, + keyword: background[:keyword], + name: background[:name], + description: value_or_empty_string(background[:description]), + line: background[:location][:line], type: 'background' } - @background_hash[:comments] = Formatter.create_comments_array(background.comments) unless background.comments.empty? end - def scenario(scenario) + def scenario(scenario_source, test_case) + scenario = scenario_source.type == :Scenario ? scenario_source.scenario : scenario_source.scenario_outline @test_case_hash = { - id: create_id(scenario), - keyword: scenario.keyword, - name: scenario.to_s, - description: scenario.description, - line: scenario.location.line, + id: "#{@feature_hash[:id]};#{create_id_from_scenario_source(scenario_source)}", + keyword: scenario[:keyword], + name: scenario[:name], + description: value_or_empty_string(scenario[:description]), + line: test_case.location.lines.max, type: 'scenario' } - @test_case_hash[:tags] = create_tags_array(scenario.tags) unless scenario.tags.empty? - @test_case_hash[:comments] = Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty? + @test_case_hash[:tags] = create_tags_array_from_tags_array(test_case.tags) unless test_case.tags.empty? end - def scenario_outline(scenario) - @test_case_hash = { - id: create_id(scenario) + ';' + @example_id, - keyword: scenario.keyword, - name: scenario.to_s, - description: scenario.description, - line: @row.location.line, - type: 'scenario' - } - tags = [] - tags += create_tags_array(scenario.tags) unless scenario.tags.empty? - tags += @examples_table_tags if @examples_table_tags - @test_case_hash[:tags] = tags unless tags.empty? - comments = [] - comments += Formatter.create_comments_array(scenario.comments) unless scenario.comments.empty? - comments += @examples_table_comments if @examples_table_comments - comments += @row_comments if @row_comments - @test_case_hash[:comments] = comments unless comments.empty? + private + + def value_or_empty_string(value) + value.nil? ? '' : value end - def examples_table(examples_table) - # the json file have traditionally used the header row as row 1, - # wheras cucumber-ruby-core used the first example row as row 1. - @example_id = create_id(examples_table) + ";#{@row.number + 1}" + def create_id(name) + name.downcase.tr(' ', '-') + end - @examples_table_tags = create_tags_array(examples_table.tags) unless examples_table.tags.empty? - @examples_table_comments = Formatter.create_comments_array(examples_table.comments) unless examples_table.comments.empty? + def create_id_from_scenario_source(scenario_source) + if scenario_source.type == :Scenario + create_id(scenario_source.scenario[:name]) + else + scenario_outline_name = scenario_source.scenario_outline[:name] + examples_name = scenario_source.examples[:name] + row_number = calculate_row_number(scenario_source) + "#{create_id(scenario_outline_name)};#{create_id(examples_name)};#{row_number}" + end end - def examples_table_row(row) - @row = row - @row_comments = Formatter.create_comments_array(row.comments) unless row.comments.empty? + def calculate_row_number(scenario_source) + scenario_source.examples[:table_body].each_with_index do |row, index| + return index + 2 if row == scenario_source.row + end end - private - - def create_id(element) - element.to_s.downcase.tr(' ', '-') + def create_tags_array_from_hash_array(tags) + tags_array = [] + tags.each { |tag| tags_array << { name: tag[:name], line: tag[:location][:line] } } + tags_array end - def create_tags_array(tags) + def create_tags_array_from_tags_array(tags) tags_array = [] tags.each { |tag| tags_array << { name: tag.name, line: tag.location.line } } tags_array end end - end - - def self.create_comments_array(comments) - comments_array = [] - comments.each { |comment| comments_array << { value: comment.to_s.strip, line: comment.location.line } } - comments_array end end end