Merge pull request #516 from mbj/fix/context-coverage
Fix coverage of Mutant::Context namespace
This commit is contained in:
commit
bd2f1a0d2c
11 changed files with 169 additions and 187 deletions
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue