Class: RSpec::Matchers::BuiltIn::ContainExactly::PairingsMaximizer Private
Relationships & Source Files | |
Namespace Children | |
Classes:
| |
Inherits: | Object |
Defined in: | rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb |
Overview
Once we started supporting composing matchers, the algorithm for this matcher got much more complicated. Consider this expression:
expect(["fool", "food"]).to contain_exactly(/foo/, /fool/)
This should pass (because we can pair /fool/ with “fool” and /foo/ with “food”), but the original algorithm used by this matcher would pair the first elements it could (/foo/ with “fool”), which would leave /fool/ and “food” unmatched. When we have an expected element which is a matcher that matches a superset of actual items compared to another expected element matcher, we need to consider every possible pairing.
This class is designed to maximize the number of actual/expected pairings – or, conversely, to minimize the number of unpaired items. It’s essentially a brute force solution, but with a few heuristics applied to reduce the size of the problem space:
* Any items which match none of the items in the other list are immediately
placed into the {unmatched_expected_indexes} or {unmatched_actual_indexes} array.
The extra items and missing items in the matcher failure are derived
from these arrays.
* Any items which reciprocally match only each other are paired up and not
considered further.
What’s left is only the items which match multiple items from the other list (or vice versa). From here, it performs a brute-force depth-first search, looking for a solution which pairs all elements in both lists, or, barring that, that produces the fewest unmatched items.
Constant Summary
-
NullSolution =
Starting solution that is worse than any other real solution.
Class.new do def self.worse_than?(_other) true end end
Class Method Summary
- .new(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes) ⇒ PairingsMaximizer constructor Internal use only
Instance Attribute Summary
- #actual_to_expected_matched_indexes readonly Internal use only
- #expected_to_actual_matched_indexes readonly Internal use only
- #solution readonly Internal use only
Instance Method Summary
- #find_best_solution Internal use only
- #apply_pairing_to(indeterminates, original_matches, other_list_index) private Internal use only
- #best_solution_for_pairing(expected_index, actual_index) private Internal use only
- #categorize_indexes(indexes_to_categorize, other_indexes) private Internal use only
- #reciprocal_single_match?(matches, index, other_list) ⇒ Boolean private Internal use only
Instance Attribute Details
#actual_to_expected_matched_indexes (readonly)
[ GitHub ]# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 223
attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
#expected_to_actual_matched_indexes (readonly)
[ GitHub ]# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 223
attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
#solution (readonly)
[ GitHub ]# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 223
attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
Instance Method Details
#apply_pairing_to(indeterminates, original_matches, other_list_index) (private)
[ GitHub ]# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 301
def apply_pairing_to(indeterminates, original_matches, other_list_index) indeterminates.inject({}) do |accum, index| accum[index] = original_matches[index] - [other_list_index] accum end end
#best_solution_for_pairing(expected_index, actual_index) (private)
[ GitHub ]# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 285
def best_solution_for_pairing(expected_index, actual_index) modified_expecteds = apply_pairing_to( solution.indeterminate_expected_indexes, expected_to_actual_matched_indexes, actual_index) modified_expecteds.delete(expected_index) modified_actuals = apply_pairing_to( solution.indeterminate_actual_indexes, actual_to_expected_matched_indexes, expected_index) modified_actuals.delete(actual_index) solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution end
#categorize_indexes(indexes_to_categorize, other_indexes) (private)
[ GitHub ]# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 265
def categorize_indexes(indexes_to_categorize, other_indexes) unmatched = [] indeterminate = [] indexes_to_categorize.each_pair do |index, matches| if matches.empty? unmatched << index elsif !reciprocal_single_match?(matches, index, other_indexes) indeterminate << index end end return unmatched, indeterminate end
#find_best_solution
[ GitHub ]# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 239
def find_best_solution return solution if solution.candidate? best_solution_so_far = NullSolution expected_index = solution.indeterminate_expected_indexes.first actuals = expected_to_actual_matched_indexes[expected_index] actuals.each do |actual_index| solution = best_solution_for_pairing(expected_index, actual_index) return solution if solution.ideal? best_solution_so_far = solution if best_solution_so_far.worse_than?(solution) end best_solution_so_far end
#reciprocal_single_match?(matches, index, other_list) ⇒ Boolean
(private)
# File 'rspec-expectations/lib/rspec/matchers/built_in/contain_exactly.rb', line 280
def reciprocal_single_match?(matches, index, other_list) return false unless matches.one? other_list[matches.first] == [index] end