class Cucumber::Formatter::Junit
The formatter used for --format junit
def add_fileattribute?
def add_fileattribute? return false if @config.formats.nil? || @config.formats.empty? !!@config.formats.find do |format| format.first == 'junit' && format.dig(1, 'fileattribute') == 'true' end end
def basename(feature_file)
def basename(feature_file) File.basename(feature_file.gsub(/[\\\/]/, '-'), '.feature') # rubocop:disable Style/RegexpLiteral end
def build_testcase(result, scenario_designation, output)
def build_testcase(result, scenario_designation, output) duration = ResultBuilder.new(result).test_case_duration @current_feature_data[:time] += duration classname = @current_feature_data[:feature].name filename = @current_feature_data[:uri] name = scenario_designation testcase_attributes = get_testcase_attributes(classname, name, duration, filename) @current_feature_data[:builder].testcase(testcase_attributes) do if !result.passed? && result.ok?(@config.strict) @current_feature_data[:builder].skipped @current_feature_data[:skipped] += 1 elsif !result.passed? status = result.to_sym exception = get_backtrace_object(result) @current_feature_data[:builder].failure(message: "#{status} #{name}", type: status) do @current_feature_data[:builder].cdata! output @current_feature_data[:builder].cdata!(format_exception(exception)) if exception end @current_feature_data[:failures] += 1 end @current_feature_data[:builder].tag!('system-out') do @current_feature_data[:builder].cdata! strip_control_chars(@interceptedout.buffer_string) end @current_feature_data[:builder].tag!('system-err') do @current_feature_data[:builder].cdata! strip_control_chars(@interceptederr.buffer_string) end end @current_feature_data[:tests] += 1 end
def create_output_string(test_case, scenario, result, row_name)
def create_output_string(test_case, scenario, result, row_name) scenario_source = @ast_lookup.scenario_source(test_case) keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword output = "#{keyword}: #{scenario}\n\n" return output if result.ok?(@config.strict) if scenario_source.type == :Scenario if @failing_test_step if @failing_test_step.hook? output += "#{@failing_test_step.text} at #{@failing_test_step.location}\n" else step_source = @ast_lookup.step_source(@failing_test_step).step output += "#{step_source.keyword}#{@failing_test_step.text}\n" end else # An Around hook has failed output += "Around hook\n" end else output += "Example row: #{row_name}\n" end output + "\nMessage:\n" end
def end_feature(feature_data)
def end_feature(feature_data) @testsuite = Builder::XmlMarkup.new(indent: 2) @testsuite.instruct! @testsuite.testsuite( failures: feature_data[:failures], errors: feature_data[:errors], skipped: feature_data[:skipped], tests: feature_data[:tests], time: format('%<time>.6f', time: feature_data[:time]), name: feature_data[:feature].name ) do @testsuite << feature_data[:builder].target! end write_file(feature_result_filename(feature_data[:uri]), @testsuite.target!) end
def feature_result_filename(feature_file)
def feature_result_filename(feature_file) File.join(@reportdir, "TEST-#{basename(feature_file)}.xml") end
def format_exception(exception)
def format_exception(exception) (["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n") end
def get_backtrace_object(result)
def get_backtrace_object(result) if result.failed? result.exception elsif result.backtrace result end end
def get_testcase_attributes(classname, name, duration, filename)
def get_testcase_attributes(classname, name, duration, filename) { classname: classname, name: name, time: format('%<duration>.6f', duration: duration) }.tap do |attributes| attributes[:file] = filename if add_fileattribute? end end
def initialize(config)
def initialize(config) @ast_lookup = AstLookup.new(config) 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_finished, &method(:on_test_step_finished) config.on_event :test_run_finished, &method(:on_test_run_finished) @reportdir = ensure_dir(config.out_stream, 'junit') @config = config @features_data = Hash.new do |h, k| h[k] = { feature: nil, failures: 0, errors: 0, tests: 0, skipped: 0, time: 0, builder: Builder::XmlMarkup.new(indent: 2) } end end
def on_test_case_finished(event)
def on_test_case_finished(event) test_case, result = *event.attributes result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter) test_case_name = NameBuilder.new(test_case, @ast_lookup) 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) Interceptor::Pipe.unwrap! :stdout Interceptor::Pipe.unwrap! :stderr end
def on_test_case_started(event)
def on_test_case_started(event) test_case = event.test_case start_feature(test_case) unless same_feature_as_previous_test_case?(test_case) @failing_test_step = 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 on_test_run_finished(_event)
def on_test_run_finished(_event) @features_data.each { |_file, data| end_feature(data) } end
def on_test_step_finished(event)
def on_test_step_finished(event) test_step, result = *event.attributes return if @failing_test_step @failing_test_step = test_step unless result.ok?(@config.strict) end
def same_feature_as_previous_test_case?(test_case)
def same_feature_as_previous_test_case?(test_case) @current_feature_data && @current_feature_data[:uri] == test_case.location.file end
def start_feature(test_case)
def start_feature(test_case) uri = test_case.location.file feature = @ast_lookup.gherkin_document(uri).feature raise UnNamedFeatureError, uri if feature.name.empty? @current_feature_data = @features_data[uri] @current_feature_data[:uri] = uri unless @current_feature_data[:uri] @current_feature_data[:feature] = feature unless @current_feature_data[:feature] end
def strip_control_chars(cdata)
def strip_control_chars(cdata) cdata.scan(/[[:print:]\t\n\r]/).join end
def write_file(feature_filename, data)
def write_file(feature_filename, data) File.open(feature_filename, 'w') { |file| file.write(data) } end