Skip to content

Instantly share code, notes, and snippets.

@wellavelino
Last active Sep 23, 2020
Embed
What would you like to do?
report join
require 'nokogiri'
require 'find'
class JunitMerge
FAILURES_DIR = 'artifacts/composer-output/failures/'.freeze
REPORT_DIRECTORY = 'artifacts/composer-output/'.freeze
COLLATED_REPORT = 'artifacts/composer-output/junit4-reports/collated_report.xml'.freeze
OUTDATED_REPORTS = 'artifacts/composer-output/junit4-reports'.freeze
def run
if report_list.size > 1
join_original_report(report_list)
collated_report = find_collated_report(REPORT_DIRECTORY)
handle_failure_path(collated_report.join) if File.directory?(FAILURES_DIR)
else
handle_failure_path(report_list) if File.directory?(FAILURES_DIR)
end
end
def handle_failure_path(report_path)
failures_path(FAILURES_DIR).each do |source|
merge_files(source, report_path)
end
end
# noinspection RubyModifiedFrozenObject
def join_original_report(reports)
path1 = File.read(reports[0]).encode!('UTF-8', invalid: :replace)
path2 = File.read(reports[1]).encode!('UTF-8', invalid: :replace)
first_path = Nokogiri::XML::Document.parse(path1)
second_path = Nokogiri::XML::Document.parse(path2)
first_path.xpath('//testcase/..').map do |node|
second_path.xpath('//testcase/..').map do |second_report|
%w[tests failures errors skipped retries].each do |attribute|
if (value = node[attribute])
node[attribute] = value.to_i + second_report.attributes[attribute].value.to_i
end
end
node.add_child(second_report.children)
open(COLLATED_REPORT, 'w+') {|f| f.write(node.to_s)}
end
end
delete_outdated_reports(OUTDATED_REPORTS)
end
# noinspection RubyModifiedFrozenObject
def merge_files(source_path, original_report)
source_text = File.read(source_path).encode!('UTF-8', invalid: :replace)
target_text = File.read(original_report).encode!('UTF-8', invalid: :replace)
return if target_text =~ /\A\s*\z/m
if source_text =~ /\A\s*\z/m
FileUtils.cp source_path, original_report
return
end
source = Nokogiri::XML::Document.parse(source_text)
target = Nokogiri::XML::Document.parse(target_text)
append_failures_diff(source, target)
open(original_report, 'w') {|f| f.write(target.to_s)}
end
def append_failures_diff(source, target)
source.xpath('//testsuite/testcase').each do |node|
summary_diff = SummaryDiff.new
predicates = build_predicates(node)
original = target.xpath("testsuite/testcase[#{predicates}]").first
modify_report(summary_diff, node, original) if original
modify_node_ancestors(node, summary_diff)
end
end
def build_predicates(node)
[attribute_predicate('classname', node['classname']),
attribute_predicate('name', node['name'])].join(' and ')
end
def modify_report(summary_diff, node, original)
summary_diff.add(node, 1)
summary_diff.add(original, -1)
# Uncomment when figure out dynamic retries
# node.set_attribute('retries', index += 1)
original.replace(node)
end
def modify_node_ancestors(node, summary_diff)
node.ancestors.select {|a| a.name =~ /\Atestsuite?\z/}.each do |suite|
summary_diff.apply_to(suite)
end
end
def attribute_predicate(name, value)
# XPath doesn't let you escape the delimiting quotes. Need concat() here
# to support the general case.
escaped = value.to_s.gsub('"', '", \'"\', "')
"@#{name}=concat('', \"#{escaped}\")"
end
def failures_path(sources_path)
# finding recursivly in folders and subfolders to find all xml
# files available
Find.find(sources_path).select {|p| /.*\.xml$/ =~ p}
end
def report_list
# obtain target report as String
Dir.glob('artifacts/composer-output/junit4-reports/**/*\.xml')
end
def find_collated_report(pwd = Dir.pwd)
files = []
begin
Find.find(pwd) do |path|
next if path.include? 'emulator'
next unless File.file?(path)
files << path if path.include? 'collated_report.xml'
end
rescue StandardError => e
puts 'Error reading files.', e
end
files
end
def delete_outdated_reports(path)
Dir.glob("#{path}/*").each do |file|
File.delete(file) if file.include? 'emulator'
end
end
SummaryDiff = Struct.new(:tests, :failures, :errors, :skipped, :retries) do
def initialize
self.tests = self.failures = self.errors = self.skipped = self.retries = 0
end
def add(test_node, delta)
self.tests += delta
self.failures += delta unless test_node.xpath('failure').empty?
self.errors += delta unless test_node.xpath('error').empty?
self.skipped += delta unless test_node.xpath('skipped').empty?
self.retries += delta unless test_node.xpath('retries').empty?
end
def apply_to(node)
%w[tests failures errors skipped retries].each do |attribute|
if (value = node[attribute])
node[attribute] = value.to_i + send(attribute)
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment