From 86cff4bb850d2932f1d3de6da82e74a8d25c0cf1 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Thu, 5 Sep 2019 22:15:38 +0100 Subject: [PATCH 1/2] Add instance variables to changes detected --- lib/rspec/matchers/built_in/change.rb | 15 ++++++++++++++- spec/rspec/matchers/built_in/change_spec.rb | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/rspec/matchers/built_in/change.rb b/lib/rspec/matchers/built_in/change.rb index 2641c19a4..bb68a43b6 100644 --- a/lib/rspec/matchers/built_in/change.rb +++ b/lib/rspec/matchers/built_in/change.rb @@ -367,6 +367,8 @@ def value_representation def perform_change(event_proc) @actual_before = evaluate_value_proc @before_hash = @actual_before.hash + @before_attribute_hashes = calculate_attribute_hashes(@actual_before) + yield @actual_before if block_given? return false unless Proc === event_proc @@ -374,6 +376,7 @@ def perform_change(event_proc) @actual_after = evaluate_value_proc @actual_hash = @actual_after.hash + @after_attribute_hashes = calculate_attribute_hashes(@actual_after) true end @@ -390,7 +393,9 @@ def changed? # possible for two values to be unequal (and of different classes) # but to return the same hash value. Also, some objects may change # their hash after being compared with `==`/`!=`. - @actual_before != @actual_after || @before_hash != @actual_hash + @actual_before != @actual_after || + @before_hash != @actual_hash || + @before_attribute_hashes != @after_attribute_hashes end def actual_delta @@ -399,6 +404,14 @@ def actual_delta private + # methods are stashed to ensure we get access to variables on modified classes / BasicObject + OBJECT_VARIABLES = Object.instance_method(:instance_variables) + OBJECT_VARIABLE_GET = Object.instance_method(:instance_variable_get) + + def calculate_attribute_hashes(object) + OBJECT_VARIABLES.bind(object).call.map { |name| OBJECT_VARIABLE_GET.bind(object).call(name).hash } + end + def evaluate_value_proc @value_proc ? @value_proc.call : @receiver.__send__(@message) end diff --git a/spec/rspec/matchers/built_in/change_spec.rb b/spec/rspec/matchers/built_in/change_spec.rb index 5b1221f34..73752d3f8 100644 --- a/spec/rspec/matchers/built_in/change_spec.rb +++ b/spec/rspec/matchers/built_in/change_spec.rb @@ -368,6 +368,10 @@ def self.to_s expect { @instance.some_value = 6 }.to change { @instance.some_value } end + it "passes when actual has a value modified" do + expect { @instance.some_value = 6 }.to change { @instance } + end + it "fails when actual is not modified by the block" do expect do expect {}.to change { @instance.some_value } From 91e5b65d5077ea8aeffbc98dbfe03b164ce89f19 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Mon, 9 Sep 2019 19:29:50 +0100 Subject: [PATCH 2/2] Add benchmark for large objects --- benchmarks/hashing_large_objects.rb | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 benchmarks/hashing_large_objects.rb diff --git a/benchmarks/hashing_large_objects.rb b/benchmarks/hashing_large_objects.rb new file mode 100644 index 000000000..8ad27ee77 --- /dev/null +++ b/benchmarks/hashing_large_objects.rb @@ -0,0 +1,50 @@ +require 'benchmark/ips' + +OBJECT_VARIABLES = Object.instance_method(:instance_variables) +OBJECT_VARIABLE_GET = Object.instance_method(:instance_variable_get) + +def calculate_attribute_hashes(object) + OBJECT_VARIABLES.bind(object).call.map { |name| OBJECT_VARIABLE_GET.bind(object).call(name).hash } +end + +[10, 100, 1000, 10000].each do |object_size| + klass = Class.new do + define_method(:initialize) do + object_size.times do |i| + instance_variable_set(:"@my_var_#{i}", 'a' * (rand * 100)) + end + end + end + instance = klass.new + + Benchmark.ips do |ips| + ips.report("Get hashes for object of size: #{instance.instance_variables.size}") do + calculate_attribute_hashes(instance) + end + end +end + +# Warming up -------------------------------------- +# Get hashes for object of size: 10 +# 14.412k i/100ms +# Calculating ------------------------------------- +# Get hashes for object of size: 10 +# 158.585k (± 5.2%) i/s - 792.660k in 5.012663s +# Warming up -------------------------------------- +# Get hashes for object of size: 100 +# 1.664k i/100ms +# Calculating ------------------------------------- +# Get hashes for object of size: 100 +# 17.041k (± 9.0%) i/s - 84.864k in 5.036735s +# Warming up -------------------------------------- +# Get hashes for object of size: 1000 +# 173.000 i/100ms +# Calculating ------------------------------------- +# Get hashes for object of size: 1000 +# 1.808k (± 4.6%) i/s - 9.169k in 5.082355s +# Warming up -------------------------------------- +# Get hashes for object of size: 10000 +# 16.000 i/100ms +# Calculating ------------------------------------- +# Get hashes for object of size: 10000 +# 176.298 (± 3.4%) i/s - 896.000 in 5.089070s