# frozen_string_literal: truerequire'builder'require'cucumber/formatter/backtrace_filter'require'cucumber/formatter/io'require'cucumber/formatter/interceptor'require'fileutils'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)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_caseunlesssame_feature_as_previous_test_case?(test_case.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)enddefon_test_step_finished(event)test_step,result=*event.attributesreturnif@failing_step_source@failing_step_source=test_step.source.lastunlessresult.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)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?(feature)@current_feature_data&&@current_feature_data[:feature].file==feature.file&&@current_feature_data[:feature].location==feature.locationenddefstart_feature(feature)raiseUnNamedFeatureError.new(feature.file)iffeature.name.empty?@current_feature_data=@features_data[feature.file]@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('%.6f',feature_data[:time]),:name=>feature_data[:feature].name)do@testsuite<<feature_data[:builder].target!endwrite_file(feature_result_filename(feature_data[:feature].file),@testsuite.target!)enddefcreate_output_string(test_case,scenario,result,row_name)output="#{test_case.keyword}: #{scenario}\n\n"returnoutputifresult.ok?(@config.strict)iftest_case.keyword=='Scenario'if@failing_step_sourceoutput+=@failing_step_source.keyword.to_sunlesshook?(@failing_step_source)output+="#{@failing_step_source}\n"else# An Around hook has failedoutput+="Around hook\n"endelseoutput+="Example row: #{row_name}\n"endoutput+"\nMessage:\n"enddefhook?(step)['Before hook','After hook','AfterStep hook'].include?step.textenddefbuild_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('%.6f',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?returnresult.exceptionelsifresult.backtracereturnresultelsereturnnilendenddefformat_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')enddefwrite_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)@name_suffix=''@row_name=''test_case.describe_source_toselfenddeffeature(*)selfenddefscenario(scenario)@scenario_name=(scenario.name.nil?||scenario.name=='')?'Unnamed scenario':scenario.nameselfenddefscenario_outline(outline)@scenario_name=(outline.name.nil?||outline.name=='')?'Unnamed scenario outline':outline.nameselfenddefexamples_table(*)selfenddefexamples_table_row(row)@row_name='| '+row.values.join(' | ')+' |'@name_suffix=" (outline example : #{@row_name})"selfendendclassResultBuilderattr_reader:test_case_durationdefinitialize(result)@test_case_duration=0result.describe_to(self)enddefpassed(*)enddeffailed(*)enddefundefined(*)enddefskipped(*)enddefpending(*)enddefexception(*)enddefduration(duration,*)duration.tap{|duration|@test_case_duration=duration.nanoseconds/10**9.0}endendendend