From 0881518751227eda75a4bda32f3cf5bda33538f7 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Thu, 18 Jul 2013 23:54:02 -0700 Subject: [PATCH 1/4] Refactor metadata setup code in specs. Rather than instantiating it and calling `process`, it makes more sense to pass in all the args into one method. This prepares the way for that. --- spec/rspec/core/metadata_spec.rb | 149 +++++++++++-------------------- 1 file changed, 54 insertions(+), 95 deletions(-) diff --git a/spec/rspec/core/metadata_spec.rb b/spec/rspec/core/metadata_spec.rb index c128a49089..be3086d521 100644 --- a/spec/rspec/core/metadata_spec.rb +++ b/spec/rspec/core/metadata_spec.rb @@ -26,26 +26,28 @@ module Core end - describe "#process" do - Metadata::RESERVED_KEYS.each do |key| - it "prohibits :#{key} as a hash key" do - m = Metadata.new - expect do - m.process('group', key => {}) - end.to raise_error(/:#{key} is not allowed/) - end - end + def metadata_for_group(parent_metadata, *args) + m = Metadata.new(parent_metadata) + m.process(*args) + m + end - it "uses :caller if passed as part of the user metadata" do - m = Metadata.new - m.process('group', :caller => ['example_file:42']) - expect(m[:example_group][:location]).to eq("example_file:42") + Metadata::RESERVED_KEYS.each do |key| + it "prohibits :#{key} as a hash key" do + expect do + metadata_for_group(nil, 'group', key => {}) + end.to raise_error(/:#{key} is not allowed/) end end + it "uses :caller if passed as part of the user metadata" do + m = metadata_for_group(nil, 'group', :caller => ['example_file:42']) + expect(m[:example_group][:location]).to eq("example_file:42") + end + describe "#filter_applies?" do - let(:parent_group_metadata) { Metadata.new.process('parent group', :caller => ["foo_spec.rb:#{__LINE__}"]) } - let(:group_metadata) { Metadata.new(parent_group_metadata).process('group', :caller => ["foo_spec.rb:#{__LINE__}"]) } + let(:parent_group_metadata) { metadata_for_group(nil, 'parent group', :caller => ["foo_spec.rb:#{__LINE__}"]) } + let(:group_metadata) { metadata_for_group(parent_group_metadata, 'group', :caller => ["foo_spec.rb:#{__LINE__}"]) } let(:example_metadata) { group_metadata.for_example('example', :caller => ["foo_spec.rb:#{__LINE__}"], :if => true) } let(:next_example_metadata) { group_metadata.for_example('next_example', :caller => ["foo_spec.rb:#{example_line_number + 2}"]) } let(:world) { World.new } @@ -203,7 +205,7 @@ module Core end describe "#for_example" do - let(:metadata) { Metadata.new.process("group description") } + let(:metadata) { metadata_for_group(nil, "group description") } let(:mfe) { metadata.for_example("example description", {:arbitrary => :options}) } let(:line_number) { __LINE__ - 1 } @@ -264,62 +266,47 @@ module Core describe key do context "with a String" do it "returns nil" do - m = Metadata.new - m.process('group') - + m = metadata_for_group(nil, 'group') expect(m[:example_group][key]).to be_nil end end context "with a Symbol" do it "returns nil" do - m = Metadata.new - m.process(:group) - + m = metadata_for_group(nil, :group) expect(m[:example_group][key]).to be_nil end end context "with a class" do it "returns the class" do - m = Metadata.new - m.process(String) - + m = metadata_for_group(nil, String) expect(m[:example_group][key]).to be(String) end end context "in a nested group" do it "returns the parent group's described class" do - sm = Metadata.new - sm.process(String) - - m = Metadata.new(sm) - m.process(Array) + sm = metadata_for_group(nil, String) + m = metadata_for_group(sm, Array) expect(m[:example_group][key]).to be(String) end it "returns own described class if parent doesn't have one" do - sm = Metadata.new - sm.process("foo") - - m = Metadata.new(sm) - m.process(Array) + sm = metadata_for_group(nil, "foo") + m = metadata_for_group(sm, Array) expect(m[:example_group][key]).to be(Array) end it "can override a parent group's described class" do - parent = Metadata.new - parent.process(String) + parent = metadata_for_group(nil, String) + child = metadata_for_group(parent, Fixnum) - child = Metadata.new(parent) - child.process(Fixnum) child[:example_group][key] = Hash - grandchild = Metadata.new(child) - grandchild.process(Array) + grandchild = metadata_for_group(child, Array) expect(grandchild[:example_group][key]).to be(Hash) expect(child[:example_group][key]).to be(Hash) @@ -331,8 +318,7 @@ module Core describe ":description" do it "just has the example description" do - m = Metadata.new - m.process("group") + m = metadata_for_group(nil, "group") m = m.for_example("example", {}) expect(m[:description]).to eq("example") @@ -340,36 +326,28 @@ module Core context "with a string" do it "provides the submitted description" do - m = Metadata.new - m.process("group") - + m = metadata_for_group(nil, "group") expect(m[:example_group][:description]).to eq("group") end end context "with a non-string" do it "provides the submitted description" do - m = Metadata.new - m.process("group") - + m = metadata_for_group(nil, "group") expect(m[:example_group][:description]).to eq("group") end end context "with a non-string and a string" do it "concats the args" do - m = Metadata.new - m.process(Object, 'group') - + m = metadata_for_group(nil, Object, 'group') expect(m[:example_group][:description]).to eq("Object group") end end context "with empty args" do it "returns empty string for [:example_group][:description]" do - m = Metadata.new - m.process() - + m = metadata_for_group(nil) expect(m[:example_group][:description]).to eq("") end end @@ -377,33 +355,24 @@ module Core describe ":full_description" do it "concats example group name and description" do - group_metadata = Metadata.new - group_metadata.process('group') + group_metadata = metadata_for_group(nil, 'group') example_metadata = group_metadata.for_example("example", {}) expect(example_metadata[:full_description]).to eq("group example") end it "concats nested example group descriptions" do - parent = Metadata.new - parent.process('parent') - - child = Metadata.new(parent) - child.process('child') + parent = metadata_for_group(nil, 'parent') + child = metadata_for_group(parent, 'child') expect(child[:example_group][:full_description]).to eq("parent child") expect(child.for_example('example', child)[:full_description]).to eq("parent child example") end it "concats nested example group descriptions three deep" do - grandparent = Metadata.new - grandparent.process('grandparent') - - parent = Metadata.new(grandparent) - parent.process('parent') - - child = Metadata.new(parent) - child.process('child') + grandparent = metadata_for_group(nil, 'grandparent') + parent = metadata_for_group(grandparent, 'parent') + child = metadata_for_group(parent, 'child') expect(grandparent[:example_group][:full_description]).to eq("grandparent") expect(parent[:example_group][:full_description]).to eq("grandparent parent") @@ -414,30 +383,25 @@ module Core %w[# . ::].each do |char| context "with a 2nd arg starting with #{char}" do it "removes the space" do - m = Metadata.new - m.process(Array, "#{char}method") + m = metadata_for_group(nil, Array, "#{char}method") expect(m[:example_group][:full_description]).to eq("Array#{char}method") end end context "with a description starting with #{char} nested under a module" do it "removes the space" do - parent = Metadata.new - parent.process(Object) - child = Metadata.new(parent) - child.process("#{char}method") + parent = metadata_for_group(nil, Object) + child = metadata_for_group(parent, "#{char}method") expect(child[:example_group][:full_description]).to eq("Object#{char}method") end end context "with a description starting with #{char} nested under a context string" do it "does not remove the space" do - grandparent = Metadata.new - grandparent.process(Array) - parent = Metadata.new(grandparent) - parent.process("with 2 items") - child = Metadata.new(parent) - child.process("#{char}method") + grandparent = metadata_for_group(nil, Array) + parent = metadata_for_group(grandparent, "with 2 items") + child = metadata_for_group(parent, "#{char}method") + expect(child[:example_group][:full_description]).to eq("Array with 2 items #{char}method") end end @@ -446,11 +410,11 @@ module Core describe ":file_path" do it "finds the first non-rspec lib file in the caller array" do - m = Metadata.new - m.process(:caller => [ - "./lib/rspec/core/foo.rb", - "#{__FILE__}:#{__LINE__}" + m = metadata_for_group(nil, :caller => [ + "./lib/rspec/core/foo.rb", + "#{__FILE__}:#{__LINE__}" ]) + expect(m[:example_group][:file_path]).to eq(relative_path(__FILE__)) end end @@ -463,25 +427,20 @@ module Core end it "finds the line number with the first spec file with drive letter" do - m = Metadata.new - m.process(:caller => [ "C:/path/to/file_spec.rb:#{__LINE__}" ]) + m = metadata_for_group(nil, :caller => [ "C:/path/to/file_spec.rb:#{__LINE__}" ]) expect(m[:example_group][:line_number]).to eq(__LINE__ - 1) end it "uses the number after the first : for ruby 1.9" do - m = Metadata.new - m.process(:caller => [ "#{__FILE__}:#{__LINE__}:999" ]) + m = metadata_for_group(nil, :caller => [ "#{__FILE__}:#{__LINE__}:999" ]) expect(m[:example_group][:line_number]).to eq(__LINE__ - 1) end end describe "child example group" do it "nests the parent's example group metadata" do - parent = Metadata.new - parent.process(Object, 'parent') - - child = Metadata.new(parent) - child.process() + parent = metadata_for_group(nil, Object, 'parent') + child = metadata_for_group(parent) expect(child[:example_group][:example_group]).to eq(parent[:example_group]) end From e3ca7f2c01fc82d6bd2c59ce815292ef6037efec Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Wed, 17 Jul 2013 09:24:39 -0700 Subject: [PATCH 2/4] Move code around so it's clear which methods are for which modules. --- lib/rspec/core/metadata.rb | 250 ++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/lib/rspec/core/metadata.rb b/lib/rspec/core/metadata.rb index bf6521dc2a..4eaa0d979a 100644 --- a/lib/rspec/core/metadata.rb +++ b/lib/rspec/core/metadata.rb @@ -35,131 +35,6 @@ def self.relative_path(line) nil end - # @private - module MetadataHash - - # @private - # Supports lazy evaluation of some values. Extended by - # ExampleMetadataHash and GroupMetadataHash, which get mixed in to - # Metadata for ExampleGroups and Examples (respectively). - def [](key) - store_computed(key) unless has_key?(key) - super - end - - def fetch(key, *args) - store_computed(key) unless has_key?(key) - super - end - - private - - def store_computed(key) - case key - when :location - store(:location, location) - when :file_path, :line_number - file_path, line_number = file_and_line_number - store(:file_path, file_path) - store(:line_number, line_number) - when :execution_result - store(:execution_result, {}) - when :describes, :described_class - klass = described_class - store(:described_class, klass) - # TODO (2011-11-07 DC) deprecate :describes as a key - store(:describes, klass) - when :full_description - store(:full_description, full_description) - when :description - store(:description, build_description_from(*self[:description_args])) - when :description_args - store(:description_args, []) - end - end - - def location - "#{self[:file_path]}:#{self[:line_number]}" - end - - def file_and_line_number - first_caller_from_outside_rspec =~ /(.+?):(\d+)(|:\d+)/ - return [Metadata::relative_path($1), $2.to_i] - end - - def first_caller_from_outside_rspec - self[:caller].detect {|l| l !~ /\/lib\/rspec\/core/} - end - - def method_description_after_module?(parent_part, child_part) - return false unless parent_part.is_a?(Module) - child_part =~ /^(#|::|\.)/ - end - - def build_description_from(first_part = '', *parts) - description, _ = parts.inject([first_part.to_s, first_part]) do |(desc, last_part), this_part| - this_part = this_part.to_s - this_part = (' ' + this_part) unless method_description_after_module?(last_part, this_part) - [(desc + this_part), this_part] - end - - description - end - end - - # Mixed in to Metadata for an Example (extends MetadataHash) to support - # lazy evaluation of some values. - module ExampleMetadataHash - include MetadataHash - - def described_class - self[:example_group].described_class - end - - def full_description - build_description_from(self[:example_group][:full_description], *self[:description_args]) - end - end - - # Mixed in to Metadata for an ExampleGroup (extends MetadataHash) to - # support lazy evaluation of some values. - module GroupMetadataHash - include MetadataHash - - def described_class - container_stack.each do |g| - [:described_class, :describes].each do |key| - if g.has_key?(key) - value = g[key] - return value unless value.nil? - end - end - end - - container_stack.reverse.each do |g| - candidate = g[:description_args].first - return candidate unless String === candidate || Symbol === candidate - end - - nil - end - - def full_description - build_description_from(*container_stack.reverse.map {|a| a[:description_args]}.flatten) - end - - def container_stack - @container_stack ||= begin - groups = [group = self] - while group.has_key?(:example_group) - groups << group[:example_group] - group = group[:example_group] - end - groups - end - end - end - def initialize(parent_group_metadata=nil) if parent_group_metadata update(parent_group_metadata) @@ -294,6 +169,131 @@ def relevant_line_numbers(metadata=self) [metadata[:line_number]] + (metadata[:example_group] ? relevant_line_numbers(metadata[:example_group]) : []) end + # @private + module MetadataHash + + # @private + # Supports lazy evaluation of some values. Extended by + # ExampleMetadataHash and GroupMetadataHash, which get mixed in to + # Metadata for ExampleGroups and Examples (respectively). + def [](key) + store_computed(key) unless has_key?(key) + super + end + + def fetch(key, *args) + store_computed(key) unless has_key?(key) + super + end + + private + + def store_computed(key) + case key + when :location + store(:location, location) + when :file_path, :line_number + file_path, line_number = file_and_line_number + store(:file_path, file_path) + store(:line_number, line_number) + when :execution_result + store(:execution_result, {}) + when :describes, :described_class + klass = described_class + store(:described_class, klass) + # TODO (2011-11-07 DC) deprecate :describes as a key + store(:describes, klass) + when :full_description + store(:full_description, full_description) + when :description + store(:description, build_description_from(*self[:description_args])) + when :description_args + store(:description_args, []) + end + end + + def location + "#{self[:file_path]}:#{self[:line_number]}" + end + + def file_and_line_number + first_caller_from_outside_rspec =~ /(.+?):(\d+)(|:\d+)/ + return [Metadata::relative_path($1), $2.to_i] + end + + def first_caller_from_outside_rspec + self[:caller].detect {|l| l !~ /\/lib\/rspec\/core/} + end + + def method_description_after_module?(parent_part, child_part) + return false unless parent_part.is_a?(Module) + child_part =~ /^(#|::|\.)/ + end + + def build_description_from(first_part = '', *parts) + description, _ = parts.inject([first_part.to_s, first_part]) do |(desc, last_part), this_part| + this_part = this_part.to_s + this_part = (' ' + this_part) unless method_description_after_module?(last_part, this_part) + [(desc + this_part), this_part] + end + + description + end + end + + # Mixed in to Metadata for an Example (extends MetadataHash) to support + # lazy evaluation of some values. + module ExampleMetadataHash + include MetadataHash + + def described_class + self[:example_group].described_class + end + + def full_description + build_description_from(self[:example_group][:full_description], *self[:description_args]) + end + end + + # Mixed in to Metadata for an ExampleGroup (extends MetadataHash) to + # support lazy evaluation of some values. + module GroupMetadataHash + include MetadataHash + + def described_class + container_stack.each do |g| + [:described_class, :describes].each do |key| + if g.has_key?(key) + value = g[key] + return value unless value.nil? + end + end + end + + container_stack.reverse.each do |g| + candidate = g[:description_args].first + return candidate unless String === candidate || Symbol === candidate + end + + nil + end + + def full_description + build_description_from(*container_stack.reverse.map {|a| a[:description_args]}.flatten) + end + + def container_stack + @container_stack ||= begin + groups = [group = self] + while group.has_key?(:example_group) + groups << group[:example_group] + group = group[:example_group] + end + groups + end + end + end end end end + From c776b88895f6c472b6e1a891da3a81d0dfa82c66 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Wed, 17 Jul 2013 09:27:50 -0700 Subject: [PATCH 3/4] Convert Metadata to use composition rather than inheritance. - It's best to avoid subclassing core types like Hash. - Remove runtime module extensions, since that blows MRIs method caches and has an affect on perf. - Instantiation was a two part process: `.new` followed by `process`. Conceptually, these belong together, so switch to using a single method for instantiation. Two gotchas I discovered: - The `include` matcher didn't initially work properly with `Metadata`. It uses an `is_a?(Hash)` check to know how to treat the object. To solve this, we override `is_a?` to return true for Hash. - `dup` didn't work as expected. `dup` is used to get a copy that can be modified without affecting the original; however, `dup` was simply duping the wrapping object, and each retained a reference to the same wrapped hash, causing changes on one to show up on the other. I override `dup` and `clone` to address this. --- lib/rspec/core/example_group.rb | 2 +- lib/rspec/core/metadata.rb | 71 ++++++++++++++++++++++++-------- spec/rspec/core/metadata_spec.rb | 25 ++++++++--- 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/lib/rspec/core/example_group.rb b/lib/rspec/core/example_group.rb index 94b3899fe8..0b01406f7e 100644 --- a/lib/rspec/core/example_group.rb +++ b/lib/rspec/core/example_group.rb @@ -310,7 +310,7 @@ def self.set_it_up(*args) symbol_description = args.shift if args.first.is_a?(Symbol) args << build_metadata_hash_from(args) args.unshift(symbol_description) if symbol_description - @metadata = RSpec::Core::Metadata.new(superclass_metadata).process(*args) + @metadata = RSpec::Core::Metadata.new_for_example_group(superclass_metadata, *args) hooks.register_globals(self, RSpec.configuration.hooks) world.configure_group(self) end diff --git a/lib/rspec/core/metadata.rb b/lib/rspec/core/metadata.rb index 4eaa0d979a..09d5aa3801 100644 --- a/lib/rspec/core/metadata.rb +++ b/lib/rspec/core/metadata.rb @@ -1,3 +1,5 @@ +require 'delegate' + module RSpec module Core # Each ExampleGroup class and Example instance owns an instance of @@ -24,7 +26,7 @@ module Core # @see FilterManager # @see Configuration#filter_run_including # @see Configuration#filter_run_excluding - class Metadata < Hash + class Metadata < DelegateClass(Hash) def self.relative_path(line) line = line.sub(File.expand_path("."), ".") @@ -35,19 +37,29 @@ def self.relative_path(line) nil end - def initialize(parent_group_metadata=nil) + def initialize(hash = {}) + super(hash) + yield self if block_given? + end + + def self.new_for_example_group(parent_group_metadata, *args) + new do |meta| + meta.setup_for_example_group(parent_group_metadata, *args) + end + end + + # @private + def setup_for_example_group(parent_group_metadata, *args) if parent_group_metadata update(parent_group_metadata) - store(:example_group, {:example_group => parent_group_metadata[:example_group].extend(GroupMetadataHash)}.extend(GroupMetadataHash)) + + store(:example_group, GroupMetadataHash.new( + :example_group => GroupMetadataHash.new(parent_group_metadata[:example_group]) + )) else - store(:example_group, {}.extend(GroupMetadataHash)) + store(:example_group, GroupMetadataHash.new) end - yield self if block_given? - end - - # @private - def process(*args) user_metadata = args.last.is_a?(Hash) ? args.pop : {} ensure_valid_keys(user_metadata) @@ -59,7 +71,9 @@ def process(*args) # @private def for_example(description, user_metadata) - dup.extend(ExampleMetadataHash).configure_for_example(description, user_metadata) + ExampleMetadataHash.new(dup).tap do |hash| + hash.configure_for_example(description, user_metadata) + end end # @private @@ -72,6 +86,29 @@ def all_apply?(filters) filters.all? {|k,v| filter_applies?(k,v)} end + # Report that this is a Hash (since it provides the full + # Hash interface). This is necessary so that it will work + # as expected with the `include` matcher. + def is_a?(klass) + super || __getobj__.is_a?(klass) + end + + # Ensures that `dup` works as expected. Without overriding + # it like this, the dup'd instance would retain a reference + # to the exact same hash instance, and thus would change + # the original when hash entries are changed. + def dup + super.tap { |the_dup| the_dup.__setobj__(__getobj__.dup) } + end + + # Ensures that `clone` works as expected. Without overriding + # it like this, the cloned instance would retain a reference + # to the exact same hash instance, and thus would change + # the original when hash entries are changed. + def clone + super.tap { |the_clone| the_clone.__setobj__(__getobj__.clone) } + end + # @private def filter_applies?(key, value, metadata=self) return metadata.filter_applies_to_any_value?(key, value) if Array === metadata[key] && !(Proc === value) @@ -174,8 +211,8 @@ module MetadataHash # @private # Supports lazy evaluation of some values. Extended by - # ExampleMetadataHash and GroupMetadataHash, which get mixed in to - # Metadata for ExampleGroups and Examples (respectively). + # ExampleMetadataHash and GroupMetadataHash, which which are used for + # Examples and ExampleGroups (respectively). def [](key) store_computed(key) unless has_key?(key) super @@ -241,9 +278,8 @@ def build_description_from(first_part = '', *parts) end end - # Mixed in to Metadata for an Example (extends MetadataHash) to support - # lazy evaluation of some values. - module ExampleMetadataHash + # Wraps the metadata for an Example to support lazy evaluation of some values. + class ExampleMetadataHash < Metadata include MetadataHash def described_class @@ -255,9 +291,8 @@ def full_description end end - # Mixed in to Metadata for an ExampleGroup (extends MetadataHash) to - # support lazy evaluation of some values. - module GroupMetadataHash + # Wraps the metadata for an ExampleGroup to support lazy evaluation of some values. + class GroupMetadataHash < Metadata include MetadataHash def described_class diff --git a/spec/rspec/core/metadata_spec.rb b/spec/rspec/core/metadata_spec.rb index be3086d521..4e64040200 100644 --- a/spec/rspec/core/metadata_spec.rb +++ b/spec/rspec/core/metadata_spec.rb @@ -27,9 +27,7 @@ module Core end def metadata_for_group(parent_metadata, *args) - m = Metadata.new(parent_metadata) - m.process(*args) - m + Metadata.new_for_example_group(parent_metadata, *args) end Metadata::RESERVED_KEYS.each do |key| @@ -40,6 +38,24 @@ def metadata_for_group(parent_metadata, *args) end end + it 'reports that it is a hash (so the `include` matcher can work with it)' do + m = Metadata.new(:a => 5) + expect(m.is_a?(Hash)).to be_true + expect(m).to include(:a => 5) + expect(m).not_to include(:b => 4) + end + + [:dup, :clone].each do |method| + it "#{method}s properly" do + m = Metadata.new(:a => 5) + m2 = m.send(method) + + m[:b] = 4 + expect(m[:b]).to eq(4) + expect(m2[:b]).to be_nil + end + end + it "uses :caller if passed as part of the user metadata" do m = metadata_for_group(nil, 'group', :caller => ['example_file:42']) expect(m[:example_group][:location]).to eq("example_file:42") @@ -421,8 +437,7 @@ def metadata_for_group(parent_metadata, *args) describe ":line_number" do it "finds the line number with the first non-rspec lib file in the backtrace" do - m = Metadata.new - m.process({}) + m = Metadata.new_for_example_group(nil, {}) expect(m[:example_group][:line_number]).to eq(__LINE__ - 1) end From 929dda296cd39964432b55e5095bfb9a431771cc Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Sun, 21 Jul 2013 20:20:00 +1000 Subject: [PATCH 4/4] benchmark to show DelegateClass performance degredation --- benchmarks/delegate_v_defined.rb | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 benchmarks/delegate_v_defined.rb diff --git a/benchmarks/delegate_v_defined.rb b/benchmarks/delegate_v_defined.rb new file mode 100644 index 0000000000..d6afd3f009 --- /dev/null +++ b/benchmarks/delegate_v_defined.rb @@ -0,0 +1,60 @@ +require 'benchmark' +require 'delegate' + +class Original + + def my_method + true + end + +end + +class Delegated < DelegateClass(Original) + def initialize + super(Original.new) + end +end + +class Composed + def initialize + @original = Original.new + end + + define_method(:my_method) { @original.my_method } +end + +n = 100_000 + +Benchmark.benchmark do |bm| + puts "#{n} times - ruby #{RUBY_VERSION}" + + puts "[control] straight to obj" + bm.report do + n.times do + Original.new.my_method + end + end + + puts "[delegate] DelegateClass to obj" + bm.report do + n.times do + Delegated.new.my_method + end + end + + puts "[composed] passed to obj" + bm.report do + n.times do + Composed.new.my_method + end + end + +end + +# 100000 times - ruby 2.0.0 +# [control] straight to obj +# 0.040000 0.000000 0.040000 ( 0.038468) +# [delegate] DelegateClass to obj +# 0.230000 0.000000 0.230000 ( 0.234356) +# [composed] passed to obj +# 0.070000 0.000000 0.070000 ( 0.071021)