class RSpec::Matchers::BuiltIn::ContainExactly

@api private Provides the implementation for `contain_exactly` and `match_array`. Not intended to be instantiated directly.

Public Instance Methods

description() click to toggle source

@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
failure_message() click to toggle source

@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
failure_message_when_negated() click to toggle source

@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

Private Instance Methods

best_solution() click to toggle source
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 77
def best_solution
  @best_solution ||= pairings_maximizer.find_best_solution
end
convert_actual_to_an_array() click to toggle source
# 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
extra_items() click to toggle source
# 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
match(expected, actual) click to toggle source
# 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
match_when_sorted?() click to toggle source

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
missing_items() click to toggle source
# 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
pairings_maximizer() click to toggle source
# 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
safe_sort(array) click to toggle source
# File lib/rspec/matchers/built_in/contain_exactly.rb, line 61
def safe_sort(array)
  array.sort rescue array
end