Fix constant scope mutations are emitted in

This commit is contained in:
Markus Schirp 2012-12-12 22:11:35 +01:00
parent 4f823e795c
commit 386a2bc2df
9 changed files with 75 additions and 84 deletions

View file

@ -8,6 +8,8 @@
* [feature] Mutate define and block arguments
* [feature] Mutate block arguments, inklusive pattern args
* [feature] Recurse into block bodies
* [change] Unvendor inflector use mbj-inflector from rubygems
* [fixed] Insert mutations at correct constant scope
* [fixed] Crash on mutating yield, added a noop for now
* [fixed] Crash on singleton methods defined on other than constants or self

View file

@ -8,6 +8,7 @@ group :guard do
gem 'guard', '~> 1.5.4'
gem 'guard-bundler', '~> 1.0.0'
gem 'guard-rspec', '~> 2.1.1'
gem 'rb-inotify', :git => 'https://github.com/mbj/rb-inotify'
end
group :benchmarks do

View file

@ -4,7 +4,7 @@ guard :bundler do
watch('Gemfile')
end
guard :rspec, :all_on_start => false do
guard :rspec, :all_on_start => false, :all_after_pass => false do
# run all specs if the spec_helper or supporting files files are modified
watch('spec/spec_helper.rb') { 'spec/unit' }
watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec/unit' }

View file

@ -2,41 +2,7 @@ module Mutant
class Context
# Scope context for mutation (Class or Module)
class Scope < self
include Adamantium::Flat, AbstractType, Equalizer.new(:scope, :source_path)
# Class context for mutation
class Class < self
SCOPE_CLASS = Rubinius::AST::ClassScope
KEYWORD = 'class'.freeze
end
# Module context for mutation
class Module < self
SCOPE_CLASS = Rubinius::AST::ModuleScope
KEYWORD = 'module'.freeze
end
TABLE = {
::Module => Module,
::Class => Class
}.freeze
# Build scope context from class or module
#
# @param [String] source_path
#
# @param [::Class|::Module] scope
#
# @return [Context::Scope]
#
# @api private
#
def self.build(scope, source_path)
scope_class = scope.class
klass = TABLE.fetch(scope_class) do
raise ArgumentError, "Can only build mutation scope from class or module got: #{scope_class.inspect}"
end.new(scope, source_path)
end
include Adamantium::Flat, Equalizer.new(:scope, :source_path)
# Return AST wrapping mutated node
#
@ -45,12 +11,51 @@ module Mutant
# @api private
#
def root(node)
root = root_ast
block = Rubinius::AST::Block.new(1, [node])
root.body = scope_class.new(1, root.name, block)
root
nesting.reverse.inject(node) do |current, scope|
self.class.wrap(scope, current)
end
end
# Wrap node into ast node
#
# @param [Class, Module] scope
# @param [Rubinius::AST::Node] node
#
# @return [Rubinius::AST::Class]
# if scope is of kind Class
#
# @return [Rubinius::AST::Module]
# if scope is of kind module
#
# @api private
#
def self.wrap(scope, node)
name = scope.name.split('::').last
case scope
when ::Class
::Rubinius::AST::Class.new(0, name.to_sym, nil, node)
when ::Module
::Rubinius::AST::Module.new(0, name.to_sym, node)
else
raise "Cannot wrap scope: #{scope.inspect}"
end
end
# Return nesting
#
# @return [Enumerable<Class,Module>]
#
# @api private
#
def nesting
const = ::Object
name_nesting.each_with_object([]) do |name, nesting|
const = const.const_get(name)
nesting << const
end
end
memoize :nesting
# Return unqualified name of scope
#
# @return [String]

View file

@ -103,7 +103,7 @@ module Mutant
# @api private
#
def context
Context::Scope.build(scope, source_path)
Context::Scope.new(scope, source_path)
end
# Return full ast

View file

@ -7,11 +7,11 @@ class CodeLoadingSubject
end
describe Mutant, 'code loading' do
let(:context) { Mutant::Context::Scope.build(CodeLoadingSubject,"/some/path") }
let(:node) { 'def foo; :bar; end'.to_ast }
let(:root) { context.root(node) }
let(:context) { Mutant::Context::Scope.new(CodeLoadingSubject, "/some/path") }
let(:node) { 'def foo; :bar; end'.to_ast }
let(:root) { context.root(node) }
subject { Mutant::Loader::Eval.run(root) }
subject { Mutant::Loader::Eval.run(root) }
before { subject }

View file

@ -1,29 +0,0 @@
require 'spec_helper'
describe Mutant::Context::Scope, '.build' do
subject { described_class.build(constant, path) }
let(:object) { described_class }
let(:context) { mock('Context') }
let(:path) { mock('Path') }
context 'when constant is a module' do
let(:constant) { Module.new }
it { should be_kind_of(described_class::Module) }
end
context 'when constant is a class' do
let(:constant) { Class.new }
it { should be_kind_of(described_class::Class) }
end
context 'when constant is not a class nor a module' do
let(:constant) { Object.new }
it 'should raise error' do
expect { subject }.to raise_error(ArgumentError, 'Can only build mutation scope from class or module got: Object')
end
end
end

View file

@ -3,20 +3,32 @@ require 'spec_helper'
describe Mutant::Context::Scope, '#root' do
subject { object.root(node) }
let(:object) { described_class.build(TestApp::Literal, path) }
let(:object) { described_class.new(TestApp::Literal, path) }
let(:path) { mock('Path') }
let(:node) { mock('Node') }
let(:node) { ':node'.to_ast }
let(:scope) { subject.body }
let(:scope_body) { scope.body }
it 'should wrap the ast under constant' do
scope.should be_kind_of(Rubinius::AST::ClassScope)
let(:expected_source) do
ToSource.to_source(<<-RUBY.to_ast)
module TestApp
class Literal
:node
end
end
RUBY
end
it 'should place the ast under scope inside of block' do
scope_body.should be_a(Rubinius::AST::Block)
scope_body.array.should eql([node])
scope_body.array.first.should be(node)
let(:generated_source) do
ToSource.to_source(subject)
end
let(:round_tripped_source) do
ToSource.to_source(expected_source.to_ast)
end
it 'should create correct source' do
generated_source.should eql(expected_source)
end
end

View file

@ -6,7 +6,7 @@ describe Mutant::Context::Scope, '#unqualified_name' do
let(:path) { mock('Path') }
context 'with top level constant name' do
let(:object) { described_class.build(TestApp, path) }
let(:object) { described_class.new(TestApp, path) }
it 'should return the unqualified name' do
should eql('TestApp')
@ -16,7 +16,7 @@ describe Mutant::Context::Scope, '#unqualified_name' do
end
context 'with scoped constant name' do
let(:object) { described_class.build(TestApp::Literal, path) }
let(:object) { described_class.new(TestApp::Literal, path) }
it 'should return the unqualified name' do
should eql('Literal')