From 6ad24c7888cc431c3c226fae37b021fae11e6e1a Mon Sep 17 00:00:00 2001 From: Trent Ogren Date: Fri, 4 Oct 2013 11:54:55 -0500 Subject: [PATCH 1/3] Prevent instantiation of abstract classes whose superclass isn't Object Fixes #9 --- config/reek.yml | 2 +- lib/abstract_type.rb | 40 ++++++++++--------- .../abstract_method_spec.rb | 2 +- .../abstract_singleton_method_spec.rb | 2 +- .../included_spec.rb | 4 +- .../create_new_method_spec.rb} | 20 +++++++++- 6 files changed, 46 insertions(+), 24 deletions(-) rename spec/unit/abstract_type/{class_methods => abstract_method_declarations}/abstract_method_spec.rb (91%) rename spec/unit/abstract_type/{class_methods => abstract_method_declarations}/abstract_singleton_method_spec.rb (90%) rename spec/unit/abstract_type/{class_methods => abstract_method_declarations}/included_spec.rb (75%) rename spec/unit/abstract_type/{class_methods/new_spec.rb => module_methods/create_new_method_spec.rb} (65%) diff --git a/config/reek.yml b/config/reek.yml index ec2591d..0b48444 100644 --- a/config/reek.yml +++ b/config/reek.yml @@ -47,7 +47,7 @@ UncommunicativeModuleName: NestedIterators: ignore_iterators: [] exclude: [ - 'AbstractType::ClassMethods#create_abstract_singleton_method' + 'AbstractType::AbstractMethodDeclarations#create_abstract_singleton_method' ] enabled: true max_allowed_nesting: 1 diff --git a/lib/abstract_type.rb b/lib/abstract_type.rb index 261e81a..3bb4a3b 100644 --- a/lib/abstract_type.rb +++ b/lib/abstract_type.rb @@ -13,31 +13,35 @@ module AbstractType # @api private def self.included(descendant) super - descendant.extend(ClassMethods) + create_new_method(descendant) + descendant.extend(AbstractMethodDeclarations) end private_class_method :included - module ClassMethods - - # Instantiate a new object - # - # Ensures that the instance cannot be of the abstract type - # and must be a descendant. - # - # @example - # object = AbstractType.new - # - # @return [Object] - # - # @api public - def new(*) - if superclass.equal?(Object) + # Define the new method on the abstract type + # + # Ensures that the instance cannot be of the abstract type + # and must be a descendant. + # + # @param [Class] abstract_class + # + # @return [undefined] + # + # @api private + def self.create_new_method(abstract_class) + abstract_class.define_singleton_method(:new) do |*args, &block| + if equal?(abstract_class) raise NotImplementedError, "#{inspect} is an abstract type" else - super + super(*args, &block) end end + end + + private_class_method :create_new_method + + module AbstractMethodDeclarations # Create abstract instance methods # @@ -109,5 +113,5 @@ def create_abstract_instance_method(name) end end - end # module ClassMethods + end # module AbstractMethodDeclarations end # module AbstractType diff --git a/spec/unit/abstract_type/class_methods/abstract_method_spec.rb b/spec/unit/abstract_type/abstract_method_declarations/abstract_method_spec.rb similarity index 91% rename from spec/unit/abstract_type/class_methods/abstract_method_spec.rb rename to spec/unit/abstract_type/abstract_method_declarations/abstract_method_spec.rb index d759e27..3a94025 100644 --- a/spec/unit/abstract_type/class_methods/abstract_method_spec.rb +++ b/spec/unit/abstract_type/abstract_method_declarations/abstract_method_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe AbstractType::ClassMethods, '#abstract_method' do +describe AbstractType::AbstractMethodDeclarations, '#abstract_method' do subject { object.abstract_method(:some_method) } let(:object) { Class.new { include AbstractType } } diff --git a/spec/unit/abstract_type/class_methods/abstract_singleton_method_spec.rb b/spec/unit/abstract_type/abstract_method_declarations/abstract_singleton_method_spec.rb similarity index 90% rename from spec/unit/abstract_type/class_methods/abstract_singleton_method_spec.rb rename to spec/unit/abstract_type/abstract_method_declarations/abstract_singleton_method_spec.rb index 42b9ea2..321def6 100644 --- a/spec/unit/abstract_type/class_methods/abstract_singleton_method_spec.rb +++ b/spec/unit/abstract_type/abstract_method_declarations/abstract_singleton_method_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe AbstractType::ClassMethods, '#abstract_singleton_method' do +describe AbstractType::AbstractMethodDeclarations, '#abstract_singleton_method' do subject { object.abstract_singleton_method(:some_method) } let(:object) { Class.new { include AbstractType } } diff --git a/spec/unit/abstract_type/class_methods/included_spec.rb b/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb similarity index 75% rename from spec/unit/abstract_type/class_methods/included_spec.rb rename to spec/unit/abstract_type/abstract_method_declarations/included_spec.rb index aa764ec..9881fc0 100644 --- a/spec/unit/abstract_type/class_methods/included_spec.rb +++ b/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb @@ -9,9 +9,9 @@ let(:klass) { Class.new } it 'extends the klass' do - klass.singleton_class.should_not include(described_class::ClassMethods) + klass.singleton_class.should_not include(described_class::AbstractMethodDeclarations) klass.send(:include, subject) - klass.singleton_class.should include(described_class::ClassMethods) + klass.singleton_class.should include(described_class::AbstractMethodDeclarations) end it 'delegates to the ancestor' do diff --git a/spec/unit/abstract_type/class_methods/new_spec.rb b/spec/unit/abstract_type/module_methods/create_new_method_spec.rb similarity index 65% rename from spec/unit/abstract_type/class_methods/new_spec.rb rename to spec/unit/abstract_type/module_methods/create_new_method_spec.rb index de629db..6fcab32 100644 --- a/spec/unit/abstract_type/class_methods/new_spec.rb +++ b/spec/unit/abstract_type/module_methods/create_new_method_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe AbstractType::ClassMethods, '#new' do +describe AbstractType, '.create_new_method' do context 'with arguments' do subject { object.new(:foo) } @@ -46,4 +46,22 @@ def initialize(foo) specify { expect { subject }.to raise_error(NotImplementedError, "#{object} is an abstract type") } end end + + context 'on an class that doesn\'t have Object as its superclass' do + subject { object.new } + + let(:abstract_type) { Class.new(RuntimeError) { include AbstractType } } + + context 'called on a subclass' do + let(:object) { Class.new(abstract_type) } + + it { should be_instance_of(object) } + end + + context 'called on the class' do + let(:object) { abstract_type } + + specify { expect { subject }.to raise_error(NotImplementedError, "#{object} is an abstract type") } + end + end end From 62ae009bdb6c46784bb21593cb6cb5529d147e67 Mon Sep 17 00:00:00 2001 From: Trent Ogren Date: Sat, 5 Oct 2013 05:33:22 -0500 Subject: [PATCH 2/3] Fix some code formatting offences in the metrics --- .../abstract_singleton_method_spec.rb | 3 ++- .../abstract_method_declarations/included_spec.rb | 6 ++++-- .../abstract_type/module_methods/create_new_method_spec.rb | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/spec/unit/abstract_type/abstract_method_declarations/abstract_singleton_method_spec.rb b/spec/unit/abstract_type/abstract_method_declarations/abstract_singleton_method_spec.rb index bde0475..6aa8e88 100644 --- a/spec/unit/abstract_type/abstract_method_declarations/abstract_singleton_method_spec.rb +++ b/spec/unit/abstract_type/abstract_method_declarations/abstract_singleton_method_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -describe AbstractType::AbstractMethodDeclarations, '#abstract_singleton_method' do +describe AbstractType::AbstractMethodDeclarations, + '#abstract_singleton_method' do subject { object.abstract_singleton_method(:some_method) } let(:object) { Class.new { include AbstractType } } diff --git a/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb b/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb index a26d029..0aa6fc6 100644 --- a/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb +++ b/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb @@ -9,9 +9,11 @@ let(:klass) { Class.new } it 'extends the klass' do - expect(klass.singleton_class).to_not include(described_class::AbstractMethodDeclarations) + expect(klass.singleton_class) + .to_not include(described_class::AbstractMethodDeclarations) klass.send(:include, subject) - expect(klass.singleton_class).to include(described_class::AbstractMethodDeclarations) + expect(klass.singleton_class) + .to include(described_class::AbstractMethodDeclarations) end it 'delegates to the ancestor' do diff --git a/spec/unit/abstract_type/module_methods/create_new_method_spec.rb b/spec/unit/abstract_type/module_methods/create_new_method_spec.rb index 7a99e72..7efeeeb 100644 --- a/spec/unit/abstract_type/module_methods/create_new_method_spec.rb +++ b/spec/unit/abstract_type/module_methods/create_new_method_spec.rb @@ -71,7 +71,10 @@ def initialize(foo) context 'called on the class' do let(:object) { abstract_type } - specify { expect { subject }.to raise_error(NotImplementedError, "#{object} is an abstract type") } + specify do + expect { subject } + .to raise_error(NotImplementedError, "#{object} is an abstract type") + end end end end From 667fbda325ec75f6c0573b087ca03d9d4da48836 Mon Sep 17 00:00:00 2001 From: Trent Ogren Date: Sat, 5 Oct 2013 05:55:10 -0500 Subject: [PATCH 3/3] Killed the mutants --- spec/shared/create_new_method_shared_spec.rb | 20 +++++++ .../included_spec.rb | 6 ++ .../module_methods/create_new_method_spec.rb | 56 +++++-------------- 3 files changed, 41 insertions(+), 41 deletions(-) create mode 100644 spec/shared/create_new_method_shared_spec.rb diff --git a/spec/shared/create_new_method_shared_spec.rb b/spec/shared/create_new_method_shared_spec.rb new file mode 100644 index 0000000..b699c15 --- /dev/null +++ b/spec/shared/create_new_method_shared_spec.rb @@ -0,0 +1,20 @@ +# encoding: utf-8 + +shared_examples 'AbstractType.create_new_method' do + context 'called on a subclass' do + let(:object) { Class.new(abstract_type) } + + it { should be_instance_of(object) } + end + + context 'called on the class' do + let(:object) { abstract_type } + + specify do + expect { subject }.to raise_error( + NotImplementedError, + "#{object} is an abstract type" + ) + end + end +end diff --git a/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb b/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb index 0aa6fc6..f324385 100644 --- a/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb +++ b/spec/unit/abstract_type/abstract_method_declarations/included_spec.rb @@ -16,6 +16,12 @@ .to include(described_class::AbstractMethodDeclarations) end + it 'overrides the new singleton method' do + expect(klass.method(:new).owner).to eq(Class) + klass.send(:include, subject) + expect(klass.method(:new).owner).to eq(klass.singleton_class) + end + it 'delegates to the ancestor' do included_ancestor = false subject.extend Module.new { diff --git a/spec/unit/abstract_type/module_methods/create_new_method_spec.rb b/spec/unit/abstract_type/module_methods/create_new_method_spec.rb index 7efeeeb..2a69622 100644 --- a/spec/unit/abstract_type/module_methods/create_new_method_spec.rb +++ b/spec/unit/abstract_type/module_methods/create_new_method_spec.rb @@ -16,22 +16,24 @@ def initialize(foo) end end - context 'called on a subclass' do - let(:object) { Class.new(abstract_type) } + it_behaves_like 'AbstractType.create_new_method' + end - it { should be_instance_of(object) } - end + context 'with a block' do + subject { object.new(:foo) { nil } } - context 'called on the class' do - let(:object) { abstract_type } + let(:abstract_type) do + Class.new do + include AbstractType - specify do - expect { subject }.to raise_error( - NotImplementedError, - "#{object} is an abstract type" - ) + def initialize(foo) + @foo = foo + yield + end end end + + it_behaves_like 'AbstractType.create_new_method' end context 'without arguments' do @@ -39,22 +41,7 @@ def initialize(foo) let(:abstract_type) { Class.new { include AbstractType } } - context 'called on a subclass' do - let(:object) { Class.new(abstract_type) } - - it { should be_instance_of(object) } - end - - context 'called on the class' do - let(:object) { abstract_type } - - specify do - expect { subject }.to raise_error( - NotImplementedError, - "#{object} is an abstract type" - ) - end - end + it_behaves_like 'AbstractType.create_new_method' end context 'on an class that doesn\'t have Object as its superclass' do @@ -62,19 +49,6 @@ def initialize(foo) let(:abstract_type) { Class.new(RuntimeError) { include AbstractType } } - context 'called on a subclass' do - let(:object) { Class.new(abstract_type) } - - it { should be_instance_of(object) } - end - - context 'called on the class' do - let(:object) { abstract_type } - - specify do - expect { subject } - .to raise_error(NotImplementedError, "#{object} is an abstract type") - end - end + it_behaves_like 'AbstractType.create_new_method' end end