@api private Provides the implementation for `contain_exactly` and `match_array`. Not intended to be instantiated directly.
@api private @return [String]
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 32 def description "contain exactly#{to_sentence(surface_descriptions_in expected)}" end
@api private @return [String]
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 11 def failure_message if Array === actual message = "expected collection contained: #{safe_sort(surface_descriptions_in expected).inspect}\n" message += "actual collection contained: #{safe_sort(actual).inspect}\n" message += "the missing elements were: #{safe_sort(surface_descriptions_in missing_items).inspect}\n" unless missing_items.empty? message += "the extra elements were: #{safe_sort(extra_items).inspect}\n" unless extra_items.empty? message else "expected a collection that can be converted to an array with " + "`#to_ary` or `#to_a`, but got #{actual.inspect}" end end
@api private @return [String]
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 26 def failure_message_when_negated "`contain_exactly` does not support negation" end
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 77 def best_solution @best_solution ||= pairings_maximizer.find_best_solution end
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 51 def convert_actual_to_an_array if actual.respond_to?(:to_ary) @actual = actual.to_ary elsif enumerable?(actual) && actual.respond_to?(:to_a) @actual = actual.to_a else return false end end
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 71 def extra_items @extra_items ||= best_solution.unmatched_actual_indexes.map do |index| actual[index] end end
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 38 def match(expected, actual) convert_actual_to_an_array or return false match_when_sorted? || (extra_items.empty? && missing_items.empty?) end
This cannot always work (e.g. when dealing with unsortable items, or matchers as expected items), but it's practically free compared to the slowness of the full matching algorithm, and in common cases this works, so it's worth a try.
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 47 def match_when_sorted? values_match?(safe_sort(expected), safe_sort(actual)) end
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 65 def missing_items @missing_items ||= best_solution.unmatched_expected_indexes.map do |index| expected[index] end end
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 81 def pairings_maximizer @pairings_maximizer ||= begin expected_matches = {} actual_matches = {} expected.each_with_index do |e, ei| expected_matches[ei] ||= [] actual.each_with_index do |a, ai| actual_matches[ai] ||= [] # Normally we'd call `values_match?(e, a)` here but that contains # some extra checks we don't need (e.g. to support nested data # structures), and given that it's called N*M times here, it helps # perf significantly to implement the matching bit ourselves. if (e === a || a == e) expected_matches[ei] << ai actual_matches[ai] << ei end end end PairingsMaximizer.new(expected_matches, actual_matches) end end
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 61 def safe_sort(array) array.sort rescue array end