benchmarks/include_matcher.rb
require 'benchmark/ips'
require 'rspec/expectations'
include RSpec::Matchers
module RSpec
module Matchers
module BuiltIn
class OldInclude < BaseMatcher
def initialize(*expected)
@expected = expected
end
def matches?(actual)
@actual = actual
perform_match(:all?, :all?)
end
def does_not_match?(actual)
@actual = actual
perform_match(:none?, :any?)
end
def description
described_items = surface_descriptions_in(expected)
item_list = EnglishPhrasing.list(described_items)
improve_hash_formatting "include#{item_list}"
end
def failure_message
improve_hash_formatting(super) + invalid_object_message
end
def failure_message_when_negated
improve_hash_formatting(super) + invalid_object_message
end
def diffable?
!diff_would_wrongly_highlight_matched_item?
end
private
def invalid_object_message
return '' if actual.respond_to?(:include?)
", but it does not respond to `include?`"
end
def perform_match(predicate, hash_subset_predicate)
return false unless actual.respond_to?(:include?)
expected.__send__(predicate) do |expected_item|
if comparing_hash_to_a_subset?(expected_item)
expected_item.__send__(hash_subset_predicate) do |(key, value)|
actual_hash_includes?(key, value)
end
elsif comparing_hash_keys?(expected_item)
actual_hash_has_key?(expected_item)
else
actual_collection_includes?(expected_item)
end
end
end
def comparing_hash_to_a_subset?(expected_item)
actual.is_a?(Hash) && expected_item.is_a?(Hash)
end
def actual_hash_includes?(expected_key, expected_value)
actual_value = actual.fetch(expected_key) { return false }
values_match?(expected_value, actual_value)
end
def comparing_hash_keys?(expected_item)
actual.is_a?(Hash) && !expected_item.is_a?(Hash)
end
def actual_hash_has_key?(expected_key)
actual.key?(expected_key) ||
actual.keys.any? { |key| values_match?(expected_key, key) }
end
def actual_collection_includes?(expected_item)
return true if actual.include?(expected_item)
return false unless actual.respond_to?(:any?)
actual.any? { |value| values_match?(expected_item, value) }
end
def diff_would_wrongly_highlight_matched_item?
return false unless actual.is_a?(String) && expected.is_a?(Array)
lines = actual.split("\n")
expected.any? do |str|
actual.include?(str) && lines.none? { |line| line == str }
end
end
end
end
def old_include(*expected)
BuiltIn::OldInclude.new(*expected)
end
end
end
array_sizes = [10, 50, 100, 500]
# *maniacal laugh*
class << self; alias_method :inc, :include; remove_method :include; end
Benchmark.ips do |x|
x.report("Old `to include` successes") do
array_sizes.each do |n|
expect([*1..n]).to old_include(*n/2..n)
end
end
x.report("New `to include` successes") do
array_sizes.each do |n|
expect([*1..n]).to include(*n/2..n)
end
end
x.compare!
end
Benchmark.ips do |x|
x.report("Old `to include` failures") do
array_sizes.each do |n|
begin
expect([*1..n]).to old_include(*n+1..n*1.5)
rescue RSpec::Expectations::ExpectationNotMetError
end
end
end
x.report("New `to include` failures") do
array_sizes.each do |n|
begin
expect([*1..n]).to include(*n+1..n*1.5)
rescue RSpec::Expectations::ExpectationNotMetError
end
end
end
x.compare!
end
Benchmark.ips do |x|
x.report("Old `to not include` successes") do
array_sizes.each do |n|
expect([*1..n]).to_not old_include(*n+1..n*1.5)
end
end
x.report("New `to not include` successes") do
array_sizes.each do |n|
expect([*1..n]).to_not include(*n+1..n*1.5)
end
end
x.compare!
end
Benchmark.ips do |x|
x.report("Old `to not include` failures") do
array_sizes.each do |n|
begin
expect([*1..n]).to_not old_include(*n/2..n)
rescue RSpec::Expectations::ExpectationNotMetError
end
end
end
x.report("New `to not include` failures") do
array_sizes.each do |n|
begin
expect([*1..n]).to_not include(*n/2..n)
rescue RSpec::Expectations::ExpectationNotMetError
end
end
end
x.compare!
end
__END__
Calculating -------------------------------------
Old `to include` successes
30.000 i/100ms
New `to include` successes
28.000 i/100ms
-------------------------------------------------
Old `to include` successes
307.740 (± 4.2%) i/s - 1.560k
New `to include` successes
299.321 (± 2.7%) i/s - 1.512k
Comparison:
Old `to include` successes: 307.7 i/s
New `to include` successes: 299.3 i/s - 1.03x slower
Calculating -------------------------------------
Old `to include` failures
2.000 i/100ms
New `to include` failures
1.000 i/100ms
-------------------------------------------------
Old `to include` failures
20.611 (± 4.9%) i/s - 104.000
New `to include` failures
2.990 (± 0.0%) i/s - 15.000
Comparison:
Old `to include` failures: 20.6 i/s
New `to include` failures: 3.0 i/s - 6.89x slower
Calculating -------------------------------------
Old `to not include` successes
1.000 i/100ms
New `to not include` successes
1.000 i/100ms
-------------------------------------------------
Old `to not include` successes
3.505 (± 0.0%) i/s - 18.000
New `to not include` successes
3.475 (± 0.0%) i/s - 18.000
Comparison:
Old `to not include` successes: 3.5 i/s
New `to not include` successes: 3.5 i/s - 1.01x slower
Calculating -------------------------------------
Old `to not include` failures
2.000 i/100ms
New `to include` failures
1.000 i/100ms
-------------------------------------------------
Old `to not include` failures
21.187 (± 4.7%) i/s - 106.000
New `to include` failures
19.899 (± 5.0%) i/s - 100.000
Comparison:
Old `to not include` failures: 21.2 i/s
New `to include` failures: 19.9 i/s - 1.06x slower