# frozen_string_literal: truerequire'builder'require'cucumber/formatter/backtrace_filter'require'cucumber/formatter/io'require'cucumber/formatter/interceptor'require'fileutils'require'cucumber/formatter/ast_lookup'moduleCucumbermoduleFormatter# The formatter used for <tt>--format junit</tt>classJunitincludeIoclassUnNamedFeatureError<StandardErrordefinitialize(feature_file)super("The feature in '#{feature_file}' does not have a name. The JUnit XML format requires a name for the testsuite element.")endenddefinitialize(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.newdo|h,k|h[k]={feature: nil,failures: 0,errors: 0,tests: 0,skipped: 0,time: 0,builder: Builder::XmlMarkup.new(indent: 2)}endenddefon_test_case_started(event)test_case=event.test_casestart_feature(test_case)unlesssame_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)enddefon_test_step_finished(event)test_step,result=*event.attributesreturnif@failing_test_step@failing_test_step=test_stepunlessresult.ok?(@config.strict)enddefon_test_case_finished(event)test_case,result=*event.attributesresult=result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)test_case_name=NameBuilder.new(test_case,@ast_lookup)scenario=test_case_name.scenario_namescenario_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!:stdoutInterceptor::Pipe.unwrap!:stderrenddefon_test_run_finished(_event)@features_data.each{|_file,data|end_feature(data)}endprivatedefsame_feature_as_previous_test_case?(test_case)@current_feature_data&&@current_feature_data[:uri]==test_case.location.fileenddefstart_feature(test_case)uri=test_case.location.filefeature=@ast_lookup.gherkin_document(uri).featureraiseUnNamedFeatureError,uriiffeature.name.empty?@current_feature_data=@features_data[uri]@current_feature_data[:uri]=uriunless@current_feature_data[:uri]@current_feature_data[:feature]=featureunless@current_feature_data[:feature]enddefend_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!endwrite_file(feature_result_filename(feature_data[:uri]),@testsuite.target!)enddefcreate_output_string(test_case,scenario,result,row_name)# rubocop:disable Metrics/PerceivedComplexityscenario_source=@ast_lookup.scenario_source(test_case)keyword=scenario_source.type==:Scenario?scenario_source.scenario.keyword:scenario_source.scenario_outline.keywordoutput="#{keyword}: #{scenario}\n\n"returnoutputifresult.ok?(@config.strict)ifscenario_source.type==:Scenarioif@failing_test_stepif@failing_test_step.hook?output+="#{@failing_test_step.text} at #{@failing_test_step.location}\n"elsestep_source=@ast_lookup.step_source(@failing_test_step).stepoutput+="#{step_source.keyword}#{@failing_test_step.text}\n"endelse# An Around hook has failedoutput+="Around hook\n"endelseoutput+="Example row: #{row_name}\n"endoutput+"\nMessage:\n"enddefbuild_testcase(result,scenario_designation,output)duration=ResultBuilder.new(result).test_case_duration@current_feature_data[:time]+=durationclassname=@current_feature_data[:feature].namename=scenario_designation@current_feature_data[:builder].testcase(classname: classname,name: name,time: format('%<duration>.6f',duration: duration))doif!result.passed?&&result.ok?(@config.strict)@current_feature_data[:builder].skipped@current_feature_data[:skipped]+=1elsif!result.passed?status=result.to_symexception=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))ifexceptionend@current_feature_data[:failures]+=1end@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)endend@current_feature_data[:tests]+=1enddefget_backtrace_object(result)ifresult.failed?result.exceptionelsifresult.backtraceresultendenddefformat_exception(exception)(["#{exception.message} (#{exception.class})"]+exception.backtrace).join("\n")enddeffeature_result_filename(feature_file)File.join(@reportdir,"TEST-#{basename(feature_file)}.xml")enddefbasename(feature_file)File.basename(feature_file.gsub(/[\\\/]/,'-'),'.feature')# rubocop:disable Style/RegexpLiteralenddefwrite_file(feature_filename,data)File.open(feature_filename,'w'){|file|file.write(data)}end# strip control chars from cdata, to make it safe for external parsersdefstrip_control_chars(cdata)cdata.scan(/[[:print:]\t\n\r]/).joinendendclassNameBuilderattr_reader:scenario_name,:name_suffix,:row_namedefinitialize(test_case,ast_lookup)@name_suffix=''@row_name=''scenario_source=ast_lookup.scenario_source(test_case)ifscenario_source.type==:Scenarioscenario(scenario_source.scenario)elsescenario_outline(scenario_source.scenario_outline)examples_table_row(scenario_source.row)endenddefscenario(scenario)@scenario_name=scenario.name.empty??'Unnamed scenario':scenario.nameenddefscenario_outline(outline)@scenario_name=outline.name.empty??'Unnamed scenario outline':outline.nameenddefexamples_table_row(row)@row_name='| '+row.cells.map(&:value).join(' | ')+' |'@name_suffix=" (outline example : #{@row_name})"endendclassResultBuilderattr_reader:test_case_durationdefinitialize(result)@test_case_duration=0result.describe_to(self)enddefpassed(*)enddeffailed(*)enddefundefined(*)enddefskipped(*)enddefpending(*)enddefexception(*)enddefduration(duration,*)duration.tap{|dur|@test_case_duration=dur.nanoseconds/10**9.0}enddefattach(*)endendendend