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/mutator/node/match_current_line'
|
||||||
require 'mutant/loader'
|
require 'mutant/loader'
|
||||||
require 'mutant/context'
|
require 'mutant/context'
|
||||||
require 'mutant/context/scope'
|
|
||||||
require 'mutant/scope'
|
require 'mutant/scope'
|
||||||
require 'mutant/subject'
|
require 'mutant/subject'
|
||||||
require 'mutant/subject/method'
|
require 'mutant/subject/method'
|
||||||
|
|
|
@ -1,19 +1,91 @@
|
||||||
module Mutant
|
module Mutant
|
||||||
# An abstract context where mutations can be applied to.
|
# An abstract context where mutations can be applied to.
|
||||||
class Context
|
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
|
NAMESPACE_DELIMITER = '::'.freeze
|
||||||
#
|
|
||||||
# @param [Parser::AST::Node] node
|
# Return root node for mutation
|
||||||
#
|
#
|
||||||
# @return [Parser::AST::Node]
|
# @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
|
# Identification string
|
||||||
#
|
#
|
||||||
# @return [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 # Context
|
||||||
end # Mutant
|
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
|
# Target context
|
||||||
#
|
#
|
||||||
# @return [Context::Scope]
|
# @return [Context]
|
||||||
def context
|
def context
|
||||||
Context::Scope.new(scope, source_path)
|
Context.new(scope, source_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Root source node
|
# 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
|
# rubocop:disable ClosingParenthesisIndentation
|
||||||
RSpec.describe Mutant::Context::Scope do
|
RSpec.describe Mutant::Context do
|
||||||
describe '.wrap' do
|
describe '.wrap' do
|
||||||
subject { described_class.wrap(scope, node) }
|
subject { described_class.wrap(scope, node) }
|
||||||
|
|
||||||
|
@ -32,4 +32,88 @@ RSpec.describe Mutant::Context::Scope do
|
||||||
it { should eql(expected) }
|
it { should eql(expected) }
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@ RSpec.describe Mutant::Subject::Method::Instance do
|
||||||
let(:object) { described_class.new(context, node) }
|
let(:object) { described_class.new(context, node) }
|
||||||
|
|
||||||
let(:context) do
|
let(:context) do
|
||||||
Mutant::Context::Scope.new(
|
Mutant::Context.new(
|
||||||
scope,
|
scope,
|
||||||
instance_double(Pathname)
|
instance_double(Pathname)
|
||||||
)
|
)
|
||||||
|
@ -48,7 +48,7 @@ RSpec.describe Mutant::Subject::Method::Instance do
|
||||||
describe '#prepare' do
|
describe '#prepare' do
|
||||||
|
|
||||||
let(:context) do
|
let(:context) do
|
||||||
Mutant::Context::Scope.new(scope, instance_double(Pathname))
|
Mutant::Context.new(scope, instance_double(Pathname))
|
||||||
end
|
end
|
||||||
|
|
||||||
subject { object.prepare }
|
subject { object.prepare }
|
||||||
|
@ -78,7 +78,7 @@ RSpec.describe Mutant::Subject::Method::Instance::Memoized do
|
||||||
describe '#prepare' do
|
describe '#prepare' do
|
||||||
|
|
||||||
let(:context) do
|
let(:context) do
|
||||||
Mutant::Context::Scope.new(scope, double('Source Path'))
|
Mutant::Context.new(scope, double('Source Path'))
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:scope) do
|
let(:scope) do
|
||||||
|
|
|
@ -4,7 +4,7 @@ RSpec.describe Mutant::Subject::Method::Singleton do
|
||||||
let(:node) { s(:defs, s(:self), :foo, s(:args)) }
|
let(:node) { s(:defs, s(:self), :foo, s(:args)) }
|
||||||
|
|
||||||
let(:context) do
|
let(:context) do
|
||||||
Mutant::Context::Scope.new(scope, instance_double(Pathname))
|
Mutant::Context.new(scope, instance_double(Pathname))
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:scope) do
|
let(:scope) do
|
||||||
|
|
Loading…
Add table
Reference in a new issue