Merge pull request #516 from mbj/fix/context-coverage

Fix coverage of Mutant::Context namespace
This commit is contained in:
Dan Kubb 2016-01-11 15:11:41 -08:00
commit bd2f1a0d2c
11 changed files with 169 additions and 187 deletions

View file

@ -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'

View file

@ -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<Class,Module>]
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<Expression>]
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<String>]
def name_nesting
scope.name.split(NAMESPACE_DELIMITER)
end
memoize :name_nesting
end # Context
end # Mutant

View file

@ -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<Class,Module>]
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<Expression>]
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<String>]
def name_nesting
scope.name.split(NAMESPACE_DELIMITER)
end
memoize :name_nesting
end # Scope
end # Context
end # Mutant

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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