From db5dd9a030eb3c57b19e30a15259b3994d4a0ba0 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 9 Jan 2016 21:41:47 +0000 Subject: [PATCH] Fix coverage of Mutant::Context namespace * Fix Mutant::Context::Scope as only subclass of abstract Mutant::Context via onlining Scope semantics into Context. --- lib/mutant.rb | 1 - lib/mutant/context.rb | 84 +++++++++++++++-- lib/mutant/context/scope.rb | 94 ------------------- lib/mutant/matcher/method.rb | 4 +- spec/unit/mutant/context/root_spec.rb | 11 --- spec/unit/mutant/context/scope/root_spec.rb | 32 ------- .../context/scope/unqualified_name_spec.rb | 25 ----- spec/unit/mutant/context/scope_spec.rb | 11 --- spec/unit/mutant/context_spec.rb | 86 ++++++++++++++++- .../mutant/subject/method/instance_spec.rb | 6 +- .../mutant/subject/method/singleton_spec.rb | 2 +- 11 files changed, 169 insertions(+), 187 deletions(-) delete mode 100644 lib/mutant/context/scope.rb delete mode 100644 spec/unit/mutant/context/root_spec.rb delete mode 100644 spec/unit/mutant/context/scope/root_spec.rb delete mode 100644 spec/unit/mutant/context/scope/unqualified_name_spec.rb delete mode 100644 spec/unit/mutant/context/scope_spec.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index 2e7c8b50..140a9dad 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -131,7 +131,6 @@ require 'mutant/mutator/node/rescue' require 'mutant/mutator/node/match_current_line' require 'mutant/loader' require 'mutant/context' -require 'mutant/context/scope' require 'mutant/scope' require 'mutant/subject' require 'mutant/subject/method' diff --git a/lib/mutant/context.rb b/lib/mutant/context.rb index 7d399f46..52697ac8 100644 --- a/lib/mutant/context.rb +++ b/lib/mutant/context.rb @@ -1,19 +1,91 @@ module Mutant # An abstract context where mutations can be applied to. class Context - include Adamantium::Flat, AbstractType, Concord::Public.new(:source_path) + include Adamantium::Flat, Concord::Public.new(:scope, :source_path) + extend AST::Sexp - # Root ast node - # - # @param [Parser::AST::Node] node + NAMESPACE_DELIMITER = '::'.freeze + + # Return root node for mutation # # @return [Parser::AST::Node] - abstract_method :root + def root(node) + nesting.reverse.reduce(node) do |current, scope| + self.class.wrap(scope, current) + end + end # Identification string # # @return [String] - abstract_method :identification + def identification + scope.name + end + + # Wrap node into ast node + # + # @param [Class, Module] scope + # @param [Parser::AST::Node] node + # + # @return [Parser::AST::Class] + # if scope is of kind Class + # + # @return [Parser::AST::Module] + # if scope is of kind module + def self.wrap(scope, node) + name = s(:const, nil, scope.name.split(NAMESPACE_DELIMITER).last.to_sym) + case scope + when Class + s(:class, name, nil, node) + when Module + s(:module, name, node) + end + end + + # Nesting of scope + # + # @return [Enumerable] + def nesting + const = Object + name_nesting.map do |name| + const = const.const_get(name) + end + end + memoize :nesting + + # Unqualified name of scope + # + # @return [String] + def unqualified_name + name_nesting.last + end + + # Match expressions for scope + # + # @return [Enumerable] + def match_expressions + name_nesting.each_index.reverse_each.map do |index| + Expression::Namespace::Recursive.new( + scope_name: name_nesting.take(index.succ).join(NAMESPACE_DELIMITER) + ) + end + end + memoize :match_expressions + + # Scope wrapped by context + # + # @return [Module|Class] + attr_reader :scope + + private + + # Nesting of names in scope + # + # @return [Array] + def name_nesting + scope.name.split(NAMESPACE_DELIMITER) + end + memoize :name_nesting end # Context end # Mutant diff --git a/lib/mutant/context/scope.rb b/lib/mutant/context/scope.rb deleted file mode 100644 index 8b790150..00000000 --- a/lib/mutant/context/scope.rb +++ /dev/null @@ -1,94 +0,0 @@ -module Mutant - class Context - # Scope context for mutation (Class or Module) - class Scope < self - include Adamantium::Flat, Concord::Public.new(:scope, :source_path) - extend AST::Sexp - - NAMESPACE_DELIMITER = '::'.freeze - - # Return root node for mutation - # - # @return [Parser::AST::Node] - def root(node) - nesting.reverse.reduce(node) do |current, scope| - self.class.wrap(scope, current) - end - end - - # Identification string - # - # @return [String] - def identification - scope.name - end - - # Wrap node into ast node - # - # @param [Class, Module] scope - # @param [Parser::AST::Node] node - # - # @return [Parser::AST::Class] - # if scope is of kind Class - # - # @return [Parser::AST::Module] - # if scope is of kind module - def self.wrap(scope, node) - name = s(:const, nil, scope.name.split(NAMESPACE_DELIMITER).last.to_sym) - case scope - when Class - s(:class, name, nil, node) - when Module - s(:module, name, node) - end - end - - # Nesting of scope - # - # @return [Enumerable] - def nesting - const = ::Object - name_nesting.each_with_object([]) do |name, nesting| - const = const.const_get(name) - nesting << const - end - end - memoize :nesting - - # Unqualified name of scope - # - # @return [String] - def unqualified_name - name_nesting.last - end - - # Match expressions for scope - # - # @return [Enumerable] - def match_expressions - name_nesting.each_index.reverse_each.map do |index| - Expression::Namespace::Recursive.new( - scope_name: name_nesting.take(index.succ).join(NAMESPACE_DELIMITER) - ) - end - end - memoize :match_expressions - - # Scope wrapped by context - # - # @return [::Module|::Class] - attr_reader :scope - - private - - # Nesting of names in scope - # - # @return [Array] - def name_nesting - scope.name.split(NAMESPACE_DELIMITER) - end - memoize :name_nesting - - end # Scope - end # Context -end # Mutant diff --git a/lib/mutant/matcher/method.rb b/lib/mutant/matcher/method.rb index bfe65eb7..fe1d201e 100644 --- a/lib/mutant/matcher/method.rb +++ b/lib/mutant/matcher/method.rb @@ -69,9 +69,9 @@ module Mutant # Target context # - # @return [Context::Scope] + # @return [Context] def context - Context::Scope.new(scope, source_path) + Context.new(scope, source_path) end # Root source node diff --git a/spec/unit/mutant/context/root_spec.rb b/spec/unit/mutant/context/root_spec.rb deleted file mode 100644 index 45e81a24..00000000 --- a/spec/unit/mutant/context/root_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -RSpec.describe Mutant::Context, '#root' do - subject { object.root } - - let(:object) { described_class.allocate } - - it 'should raise error' do - expect do - subject - end.to raise_error('Mutant::Context#root is not implemented') - end -end diff --git a/spec/unit/mutant/context/scope/root_spec.rb b/spec/unit/mutant/context/scope/root_spec.rb deleted file mode 100644 index ce90f198..00000000 --- a/spec/unit/mutant/context/scope/root_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -RSpec.describe Mutant::Context::Scope, '#root' do - subject { object.root(node) } - - let(:object) { described_class.new(TestApp::Literal, path) } - let(:path) { instance_double(Pathname) } - let(:node) { parse(':node') } - - let(:scope) { subject.body } - let(:scope_body) { scope.body } - - let(:expected_source) do - generate(parse(<<-RUBY)) - module TestApp - class Literal - :node - end - end - RUBY - end - - let(:generated_source) do - Unparser.unparse(subject) - end - - let(:round_tripped_source) do - Unparser.unparse(parse(expected_source)) - end - - it 'should create correct source' do - expect(generated_source).to eql(expected_source) - end -end diff --git a/spec/unit/mutant/context/scope/unqualified_name_spec.rb b/spec/unit/mutant/context/scope/unqualified_name_spec.rb deleted file mode 100644 index b082b4e7..00000000 --- a/spec/unit/mutant/context/scope/unqualified_name_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -RSpec.describe Mutant::Context::Scope, '#unqualified_name' do - subject { object.unqualified_name } - - let(:path) { instance_double(Pathname) } - - context 'with top level constant name' do - let(:object) { described_class.new(TestApp, path) } - - it 'should return the unqualified name' do - should eql('TestApp') - end - - it_should_behave_like 'an idempotent method' - end - - context 'with scoped constant name' do - let(:object) { described_class.new(TestApp::Literal, path) } - - it 'should return the unqualified name' do - should eql('Literal') - end - - it_should_behave_like 'an idempotent method' - end -end diff --git a/spec/unit/mutant/context/scope_spec.rb b/spec/unit/mutant/context/scope_spec.rb deleted file mode 100644 index e948b3ec..00000000 --- a/spec/unit/mutant/context/scope_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -RSpec.describe Mutant::Context::Scope do - let(:object) { described_class.new(scope, source_path) } - let(:scope) { instance_double(Class, name: instance_double(String)) } - let(:source_path) { instance_double(Pathname) } - - describe '#identification' do - subject { object.identification } - - it { should be(scope.name) } - end -end diff --git a/spec/unit/mutant/context_spec.rb b/spec/unit/mutant/context_spec.rb index 337f1a61..ac10a150 100644 --- a/spec/unit/mutant/context_spec.rb +++ b/spec/unit/mutant/context_spec.rb @@ -1,5 +1,5 @@ # rubocop:disable ClosingParenthesisIndentation -RSpec.describe Mutant::Context::Scope do +RSpec.describe Mutant::Context do describe '.wrap' do subject { described_class.wrap(scope, node) } @@ -32,4 +32,88 @@ RSpec.describe Mutant::Context::Scope do it { should eql(expected) } end end + + let(:object) { described_class.new(scope, source_path) } + let(:scope_name) { instance_double(String) } + let(:source_path) { instance_double(Pathname) } + let(:scope) { TestApp::Literal } + + describe '#identification' do + subject { object.identification } + + it { should eql(scope.name) } + end + + describe '#root' do + subject { object.root(node) } + + let(:node) { s(:sym, :node) } + + let(:expected_source) do + generate(parse(<<-RUBY)) + module TestApp + class Literal + :node + end + end + RUBY + end + + let(:generated_source) do + Unparser.unparse(subject) + end + + let(:round_tripped_source) do + Unparser.unparse(parse(expected_source)) + end + + it 'should create correct source' do + expect(generated_source).to eql(expected_source) + end + end + + describe '#unqualified_name' do + subject { object.unqualified_name } + + let(:path) { instance_double(Pathname) } + + context 'with top level constant name' do + let(:scope) { TestApp } + + it 'should return the unqualified name' do + should eql('TestApp') + end + + it_should_behave_like 'an idempotent method' + end + + context 'with scoped constant name' do + it 'should return the unqualified name' do + should eql('Literal') + end + + it_should_behave_like 'an idempotent method' + end + end + + describe '#match_expressions' do + subject { object.match_expressions } + + context 'on toplevel scope' do + let(:scope) { TestApp } + + it { should eql([parse_expression('TestApp*')]) } + end + + context 'on nested scope' do + specify do + should eql( + [ + parse_expression('TestApp::Literal*'), + parse_expression('TestApp*') + ] + ) + end + end + end end diff --git a/spec/unit/mutant/subject/method/instance_spec.rb b/spec/unit/mutant/subject/method/instance_spec.rb index 3448f073..e32d0157 100644 --- a/spec/unit/mutant/subject/method/instance_spec.rb +++ b/spec/unit/mutant/subject/method/instance_spec.rb @@ -2,7 +2,7 @@ RSpec.describe Mutant::Subject::Method::Instance do let(:object) { described_class.new(context, node) } let(:context) do - Mutant::Context::Scope.new( + Mutant::Context.new( scope, instance_double(Pathname) ) @@ -48,7 +48,7 @@ RSpec.describe Mutant::Subject::Method::Instance do describe '#prepare' do let(:context) do - Mutant::Context::Scope.new(scope, instance_double(Pathname)) + Mutant::Context.new(scope, instance_double(Pathname)) end subject { object.prepare } @@ -78,7 +78,7 @@ RSpec.describe Mutant::Subject::Method::Instance::Memoized do describe '#prepare' do let(:context) do - Mutant::Context::Scope.new(scope, double('Source Path')) + Mutant::Context.new(scope, double('Source Path')) end let(:scope) do diff --git a/spec/unit/mutant/subject/method/singleton_spec.rb b/spec/unit/mutant/subject/method/singleton_spec.rb index d77222f0..7bef7453 100644 --- a/spec/unit/mutant/subject/method/singleton_spec.rb +++ b/spec/unit/mutant/subject/method/singleton_spec.rb @@ -4,7 +4,7 @@ RSpec.describe Mutant::Subject::Method::Singleton do let(:node) { s(:defs, s(:self), :foo, s(:args)) } let(:context) do - Mutant::Context::Scope.new(scope, instance_double(Pathname)) + Mutant::Context.new(scope, instance_double(Pathname)) end let(:scope) do