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)

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

def write_file(feature_filename, data)

def write_file(feature_filename, data)
  File.open(feature_filename, 'w') { |file| file.write(data) }
end