lib/cucumber/formatter/junit.rb in cucumber-2.0.0 vs lib/cucumber/formatter/junit.rb in cucumber-2.0.1

- old
+ new

@@ -6,184 +6,141 @@ module Cucumber module Formatter # The formatter used for <tt>--format junit</tt> class Junit - # TODO: remove coupling to types - AST_SCENARIO_OUTLINE = Core::Ast::ScenarioOutline - AST_EXAMPLE_ROW = LegacyApi::Ast::ExampleTableRow - include Io class UnNamedFeatureError < StandardError def initialize(feature_file) super("The feature in '#{feature_file}' does not have a name. The JUnit XML format requires a name for the testsuite element.") end end - def initialize(runtime, io, options) + def initialize(_runtime, io, options) @reportdir = ensure_dir(io, "junit") @options = options end - def before_feature(feature) - @current_feature = feature - @failures = @errors = @tests = @skipped = 0 - @builder = Builder::XmlMarkup.new( :indent => 2 ) - @time = 0 + def before_test_case(test_case) + unless same_feature_as_previous_test_case?(test_case.feature) + end_feature if @current_feature + start_feature(test_case.feature) + end + @failing_step_source = nil # In order to fill out <system-err/> and <system-out/>, we need to # intercept the $stderr and $stdout @interceptedout = Interceptor::Pipe.wrap(:stdout) @interceptederr = Interceptor::Pipe.wrap(:stderr) end - def before_feature_element(feature_element) - @in_examples = AST_SCENARIO_OUTLINE === feature_element - @steps_start = Time.now + def after_test_step(test_step, result) + return if @failing_step_source + + @failing_step_source = test_step.source.last unless result.ok?(@options[:strict]) end - def after_feature(feature) - @testsuite = Builder::XmlMarkup.new( :indent => 2 ) - @testsuite.instruct! - @testsuite.testsuite( - :failures => @failures, - :errors => @errors, - :skipped => @skipped, - :tests => @tests, - :time => "%.6f" % @time, - :name => @feature_name ) do - @testsuite << @builder.target! - @testsuite.tag!('system-out') do - @testsuite.cdata! strip_control_chars(@interceptedout.buffer.join) - end - @testsuite.tag!('system-err') do - @testsuite.cdata! strip_control_chars(@interceptederr.buffer.join) - end - end + def after_test_case(test_case, result) + test_case_name = NameBuilder.new(test_case) + scenario = test_case_name.scenario_name + scenario_designation = "#{scenario}#{test_case_name.name_suffix}" + output = create_output_string(test_case, scenario, result, test_case_name.row_name) + build_testcase(result, scenario_designation, output) - write_file(feature_result_filename(feature.file), @testsuite.target!) - Interceptor::Pipe.unwrap! :stdout Interceptor::Pipe.unwrap! :stderr end - def before_background(*args) - @in_background = true + def done + end_feature if @current_feature end - def after_background(*args) - @in_background = false - end + private - def feature_name(keyword, name) - raise UnNamedFeatureError.new(@current_feature.file) if name.empty? - lines = name.split(/\r?\n/) - @feature_name = lines[0] + def same_feature_as_previous_test_case?(feature) + @current_feature && @current_feature.file == feature.file && @current_feature.location == feature.location end - def scenario_name(keyword, name, file_colon_line, source_indent) - @scenario = (name.nil? || name == "") ? "Unnamed scenario" : name.split("\n")[0] - @output = "#{keyword}: #{@scenario}\n\n" + def start_feature(feature) + raise UnNamedFeatureError.new(feature.file) if feature.name.empty? + @current_feature = feature + @failures = @errors = @tests = @skipped = 0 + @builder = Builder::XmlMarkup.new(:indent => 2) + @time = 0 end - def before_steps(steps) - end - - def after_steps(steps) - return if @in_background || @in_examples - - duration = Time.now - @steps_start - if steps.failed? - steps.each { |step| @output += "#{step.keyword}#{step.name}\n" } - @output += "\nMessage:\n" + def end_feature + @testsuite = Builder::XmlMarkup.new(:indent => 2) + @testsuite.instruct! + @testsuite.testsuite( + :failures => @failures, + :errors => @errors, + :skipped => @skipped, + :tests => @tests, + :time => "%.6f" % @time, + :name => @current_feature.name ) do + @testsuite << @builder.target! end - build_testcase(duration, steps.status, steps.exception) - end - def before_examples(*args) - @header_row = true - @in_examples = true + write_file(feature_result_filename(@current_feature.file), @testsuite.target!) end - def after_examples(*args) - @in_examples = false - end - - def before_table_row(table_row) - return unless @in_examples - - @table_start = Time.now - end - - def after_table_row(table_row) - return unless @in_examples and AST_EXAMPLE_ROW === table_row - duration = Time.now - @table_start - unless @header_row - name_suffix = " (outline example : #{table_row.name})" - if table_row.failed? - @output += "Example row: #{table_row.name}\n" - @output += "\nMessage:\n" - end - build_testcase(duration, table_row.status, table_row.exception, name_suffix) + def create_output_string(test_case, scenario, result, row_name) + output = "#{test_case.keyword}: #{scenario}\n\n" + return output if result.ok?(@options[:strict]) + if test_case.keyword == "Scenario" + output += "#{@failing_step_source.keyword}" unless hook?(@failing_step_source) + output += "#{@failing_step_source.name}\n" + else + output += "Example row: #{row_name}\n" end - - @header_row = false if @header_row + output + "\nMessage:\n" end - def before_test_case(test_case) - if @options[:expand] and test_case.keyword == "Scenario Outline" - @exception = nil - end + def hook?(step) + ["Before hook", "After hook", "AfterStep hook"].include? step.name end - def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line) - if @options[:expand] and @in_examples - if not @exception and exception - @exception = exception - end - end - end - - def after_test_case(test_case, result) - if @options[:expand] and test_case.keyword == "Scenario Outline" - test_case_name = NameBuilder.new(test_case) - @scenario = test_case_name.outline_name - @output = "#{test_case.keyword}: #{@scenario}\n\n" - if result.failed? - @output += "Example row: #{test_case_name.row_name}\n" - @output += "\nMessage:\n" - end - test_case_result = ResultBuilder.new(result) - build_testcase(test_case_result.test_case_duration, test_case_result.status, @exception, test_case_name.name_suffix) - end - end - - private - - def build_testcase(duration, status, exception = nil, suffix = "") + def build_testcase(result, scenario_designation, output) + duration = ResultBuilder.new(result).test_case_duration @time += duration - classname = @feature_name - name = "#{@scenario}#{suffix}" - pending = [:pending, :undefined].include?(status) && (!@options[:strict]) + classname = @current_feature.name + name = scenario_designation @builder.testcase(:classname => classname, :name => name, :time => "%.6f" % duration) do - if status == :skipped || pending + if !result.passed? && result.ok?(@options[:strict]) @builder.skipped @skipped += 1 - elsif status != :passed - @builder.failure(:message => "#{status.to_s} #{name}", :type => status.to_s) do - @builder.cdata! @output + elsif !result.passed? + status = result.to_sym + exception = get_backtrace_object(result) + @builder.failure(:message => "#{status} #{name}", :type => status) do + @builder.cdata! output @builder.cdata!(format_exception(exception)) if exception end @failures += 1 end - @builder.tag!('system-out') - @builder.tag!('system-err') + @builder.tag!('system-out') do + @builder.cdata! strip_control_chars(@interceptedout.buffer.join) + end + @builder.tag!('system-err') do + @builder.cdata! strip_control_chars(@interceptederr.buffer.join) + end end @tests += 1 end + def get_backtrace_object(result) + if result.failed? + return result.exception + elsif result.backtrace + return result + else + return nil + end + end + def format_exception(exception) (["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n") end def feature_result_filename(feature_file) @@ -200,30 +157,33 @@ # strip control chars from cdata, to make it safe for external parsers def strip_control_chars(cdata) cdata.scan(/[[:print:]\t\n\r]/).join end - + end class NameBuilder - attr_reader :outline_name, :name_suffix, :row_name + attr_reader :scenario_name, :name_suffix, :row_name def initialize(test_case) + @name_suffix = "" + @row_name = "" test_case.describe_source_to self end def feature(*) self end - def scenario(*) + def scenario(scenario) + @scenario_name = (scenario.name.nil? || scenario.name == "") ? "Unnamed scenario" : scenario.name self end def scenario_outline(outline) - @outline_name = outline.name + @scenario_name = (outline.name.nil? || outline.name == "") ? "Unnamed scenario outline" : outline.name self end def examples_table(*) self @@ -235,40 +195,29 @@ self end end class ResultBuilder - attr_reader :status, :test_case_duration + attr_reader :test_case_duration def initialize(result) @test_case_duration = 0 result.describe_to(self) end - def passed - @status = :passed - end + def passed(*) end - def failed - @status = :failed - end + def failed(*) end - def undefined - @status = :undefined - end + def undefined(*) end - def skipped - @status = :skipped - end + def skipped(*) end - def pending(*) - @status = :pending - end + def pending(*) end - def exception(*) - end + def exception(*) end def duration(duration, *) - duration.tap { |duration| @test_case_duration = duration.nanoseconds / 10 ** 9.0 } + duration.tap { |duration| @test_case_duration = duration.nanoseconds / 10**9.0 } end end end end