debian-mirror-gitlab/spec/support/import_export/project_tree_expectations.rb
2021-04-29 21:17:54 +05:30

129 lines
4.5 KiB
Ruby

# frozen_string_literal: true
module ImportExport
module ProjectTreeExpectations
def assert_relations_match(imported_hash, exported_hash)
normalized_imported_hash = normalize_elements(imported_hash)
normalized_exported_hash = normalize_elements(exported_hash)
# this is for sanity checking, to make sure we didn't accidentally pass the test
# because we essentially ignored everything
stats = {
hashes: 0,
arrays: {
direct: 0,
pairwise: 0,
fuzzy: 0
},
values: 0
}
failures = match_recursively(normalized_imported_hash, normalized_exported_hash, stats)
puts "Elements checked:\n#{stats.pretty_inspect}"
expect(failures).to be_empty, failures.join("\n\n")
end
private
def match_recursively(left_node, right_node, stats, location_stack = [], failures = [])
if Hash === left_node && Hash === right_node
match_hashes(left_node, right_node, stats, location_stack, failures)
elsif Array === left_node && Array === right_node
match_arrays(left_node, right_node, stats, location_stack, failures)
else
stats[:values] += 1
if left_node != right_node
failures << failure_message("Value mismatch", location_stack, left_node, right_node)
end
end
failures
end
def match_hashes(left_node, right_node, stats, location_stack, failures)
stats[:hashes] += 1
left_keys = left_node.keys.to_set
right_keys = right_node.keys.to_set
if left_keys != right_keys
failures << failure_message("Hash keys mismatch", location_stack, left_keys, right_keys)
end
left_node.keys.each do |key|
location_stack << key
match_recursively(left_node[key], right_node[key], stats, location_stack, failures)
location_stack.pop
end
end
def match_arrays(left_node, right_node, stats, location_stack, failures)
has_simple_elements = left_node.none? { |el| Enumerable === el }
# for simple types, we can do a direct order-less set comparison
if has_simple_elements && left_node.to_set != right_node.to_set
stats[:arrays][:direct] += 1
failures << failure_message("Elements mismatch", location_stack, left_node, right_node)
# if both arrays have the same number of complex elements, we can compare pair-wise in-order
elsif left_node.size == right_node.size
stats[:arrays][:pairwise] += 1
left_node.zip(right_node).each do |left_entry, right_entry|
match_recursively(left_entry, right_entry, stats, location_stack, failures)
end
# otherwise we have to fall back to a best-effort match by probing into the right array;
# this means we will not account for elements that exist on the right, but not on the left
else
stats[:arrays][:fuzzy] += 1
left_node.each do |left_entry|
right_entry = right_node.find { |el| el == left_entry }
match_recursively(left_entry, right_entry, stats, location_stack, failures)
end
end
end
def failure_message(what, location_stack, left_value, right_value)
where =
if location_stack.empty?
"root"
else
location_stack.map { |loc| loc.to_sym.inspect }.join(' -> ')
end
">> [#{where}] #{what}\n\n#{left_value.pretty_inspect}\nNOT EQUAL TO\n\n#{right_value.pretty_inspect}"
end
# Helper that traverses a project tree and normalizes data that we know
# to vary in the process of importing (such as list order or row IDs)
def normalize_elements(elem)
case elem
when Hash
elem.to_h do |key, value|
if ignore_key?(key, value)
[key, :ignored]
else
[key, normalize_elements(value)]
end
end
when Array
elem.map { |a| normalize_elements(a) }
else
elem
end
end
# We currently need to ignore certain entries when checking for equivalence because
# we know them to change between imports/exports either by design or because of bugs;
# this helper filters out these problematic nodes.
def ignore_key?(key, value)
id?(key) || # IDs are known to be replaced during imports
key == 'updated_at' || # these get changed frequently during imports
key == 'next_run_at' || # these values change based on wall clock
key == 'notes' # the importer attaches an extra "by user XYZ" at the end of a note
end
def id?(key)
key == 'id' || key.ends_with?('_id')
end
end
end