lib/cucumber/formatter/json.rb in cucumber-2.0.0 vs lib/cucumber/formatter/json.rb in cucumber-2.0.1
- old
+ new
@@ -1,19 +1,298 @@
-require 'cucumber/formatter/gherkin_formatter_adapter'
+require 'multi_json'
+require 'base64'
require 'cucumber/formatter/io'
-require 'gherkin/formatter/argument'
-require 'gherkin/formatter/json_formatter'
+require 'cucumber/formatter/hook_query_visitor'
module Cucumber
module Formatter
# The formatter used for <tt>--format json</tt>
- class Json < GherkinFormatterAdapter
+ class Json
include Io
- def initialize(runtime, io, options)
- @io = ensure_io(io, "json")
- super(Gherkin::Formatter::JSONFormatter.new(@io), false, options)
+ def initialize(runtime, io, _options)
+ @runtime = runtime
+ @io = ensure_io(io, 'json')
+ @feature_hashes = []
end
+
+ def before_test_case(test_case)
+ builder = Builder.new(test_case)
+ unless same_feature_as_previous_test_case?(test_case.feature)
+ @feature_hash = builder.feature_hash
+ @feature_hashes << @feature_hash
+ end
+ @test_case_hash = builder.test_case_hash
+ if builder.background?
+ feature_elements << builder.background_hash
+ @element_hash = builder.background_hash
+ else
+ feature_elements << @test_case_hash
+ @element_hash = @test_case_hash
+ end
+ end
+
+ def before_test_step(test_step)
+ return if internal_hook?(test_step)
+ hook_query = HookQueryVisitor.new(test_step)
+ if hook_query.hook?
+ @step_or_hook_hash = {}
+ hooks_of_type(hook_query) << @step_or_hook_hash
+ return
+ end
+ if first_step_after_background?(test_step)
+ feature_elements << @test_case_hash
+ @element_hash = @test_case_hash
+ end
+ @step_or_hook_hash = create_step_hash(test_step.source.last)
+ steps << @step_or_hook_hash
+ @step_hash = @step_or_hook_hash
+ end
+
+ def after_test_step(test_step, result)
+ return if internal_hook?(test_step)
+ add_match_and_result(test_step, result)
+ end
+
+ def done
+ @io.write(MultiJson.dump(@feature_hashes, pretty: true))
+ end
+
+ def puts(message)
+ test_step_output << message
+ end
+
+ def embed(src, mime_type, _label)
+ if File.file?(src)
+ content = File.open(src, 'rb') { |f| f.read }
+ data = encode64(content)
+ else
+ if mime_type =~ /;base64$/
+ mime_type = mime_type[0..-8]
+ data = src
+ else
+ data = encode64(src)
+ end
+ 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
+ end
+
+ def first_step_after_background?(test_step)
+ test_step.source[1].name != @element_hash[:name]
+ end
+
+ def internal_hook?(test_step)
+ test_step.source.last.location.file.include?('lib/cucumber/')
+ end
+
+ def current_feature
+ @feature_hash ||= {}
+ 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
+ else
+ fail 'Unkown hook type ' + hook_query.type.to_s
+ end
+ end
+
+ def before_hooks
+ @element_hash[:before] ||= []
+ end
+
+ def after_hooks
+ @element_hash[:after] ||= []
+ end
+
+ def after_step_hooks
+ @step_hash[:after] ||= []
+ end
+
+ def test_step_output
+ @step_or_hook_hash[:output] ||= []
+ end
+
+ def test_step_embeddings
+ @step_or_hook_hash[:embeddings] ||= []
+ end
+
+ def create_step_hash(step_source)
+ step_hash = {
+ keyword: step_source.keyword,
+ name: step_source.name,
+ line: step_source.location.line
+ }
+ 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
+ end
+
+ def create_doc_string_hash(doc_string)
+ {
+ value: doc_string.content,
+ content_type: doc_string.content_type,
+ line: doc_string.location.line
+ }
+ end
+
+ def add_match_and_result(test_step, result)
+ @step_or_hook_hash[:match] = create_match_hash(test_step, result)
+ @step_or_hook_hash[:result] = create_result_hash(result)
+ end
+
+ def create_match_hash(test_step, result)
+ { location: test_step.action_location }
+ end
+
+ def create_result_hash(result)
+ result_hash = {
+ status: result.to_sym
+ }
+ result_hash[:error_message] = create_error_message(result) if result.failed? || result.pending?
+ result.duration.tap { |duration| result_hash[:duration] = duration.nanoseconds }
+ result_hash
+ end
+
+ def create_error_message(result)
+ message_element = result.failed? ? result.exception : result
+ message = "#{message_element.message} (#{message_element.class})"
+ ([message] + message_element.backtrace).join("\n")
+ end
+
+ def encode64(data)
+ # strip newlines from the encoded data
+ Base64.encode64(data).gsub(/\n/, '')
+ end
+
+ class Builder
+ attr_reader :feature_hash, :background_hash, :test_case_hash
+
+ def initialize(test_case)
+ @background_hash = nil
+ test_case.describe_source_to(self)
+ test_case.feature.background.describe_to(self)
+ end
+
+ def background?
+ @background_hash != nil
+ end
+
+ def feature(feature)
+ @feature_hash = {
+ uri: feature.file,
+ id: create_id(feature),
+ keyword: feature.keyword,
+ name: feature.name,
+ description: feature.description,
+ line: feature.location.line
+ }
+ unless feature.tags.empty?
+ @feature_hash[:tags] = create_tags_array(feature.tags)
+ if @test_case_hash[:tags]
+ @test_case_hash[:tags] = @feature_hash[:tags] + @test_case_hash[:tags]
+ else
+ @test_case_hash[:tags] = @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] + ';')
+ end
+
+ def background(background)
+ @background_hash = {
+ keyword: background.keyword,
+ name: background.name,
+ description: 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)
+ @test_case_hash = {
+ id: create_id(scenario),
+ keyword: scenario.keyword,
+ name: scenario.name,
+ description: scenario.description,
+ line: scenario.location.line,
+ 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?
+ end
+
+ def scenario_outline(scenario)
+ @test_case_hash = {
+ id: create_id(scenario) + ';' + @example_id,
+ keyword: scenario.keyword,
+ name: scenario.name,
+ 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?
+ 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}"
+
+ @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?
+ end
+
+ def examples_table_row(row)
+ @row = row
+ @row_comments = Formatter.create_comments_array(row.comments) unless row.comments.empty?
+ end
+
+ private
+
+ def create_id(element)
+ element.name.downcase.gsub(/ /, '-')
+ end
+
+ def create_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
-