Port method extraction to parser based ast

This commit is contained in:
Markus Schirp 2013-06-04 19:22:33 +02:00
parent 6747e636cd
commit fca5b8a168
16 changed files with 120 additions and 53 deletions

View file

@ -4,7 +4,7 @@ guard :bundler do
watch('Gemfile')
end
guard :rspec, :all_on_start => false, :all_after_pass => false do
guard :rspec, :cli => '--fail-fast', :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

@ -8,6 +8,7 @@ require 'equalizer'
require 'digest/sha1'
require 'inflecto'
require 'parser'
require 'parser/current'
require 'unparser'
require 'ice_nine'
require 'diff/lcs'
@ -20,6 +21,7 @@ require 'concord'
module Mutant
end
require 'mutant/node_helpers'
require 'mutant/singleton_methods'
require 'mutant/constants'
require 'mutant/support/method_object'

View file

@ -3,6 +3,7 @@ module Mutant
# Scope context for mutation (Class or Module)
class Scope < self
include Adamantium::Flat, Equalizer.new(:scope, :source_path)
extend NodeHelpers
# Return AST wrapping mutated node
#
@ -40,12 +41,12 @@ module Mutant
# @api private
#
def self.wrap(scope, node)
name = scope.name.split('::').last.to_sym
name = s(:const, nil, scope.name.split('::').last.to_sym)
case scope
when ::Class
::Rubinius::AST::Class.new(0, name, nil, node)
s(:class, name, nil, node)
when ::Module
::Rubinius::AST::Module.new(0, name, node)
s(:module, name, node)
else
raise "Cannot wrap scope: #{scope.inspect}"
end

View file

@ -50,7 +50,7 @@ module Mutant
# @api private
#
def source
ToSource.to_source(@root)
Unparser.unparse(@root)
end
end # Eval

View file

@ -78,7 +78,7 @@ module Mutant
# @api private
#
def ast
File.read(source_path).to_ast
Parser::CurrentRuby.parse(File.read(source_path))
end
# Return path to source
@ -128,19 +128,47 @@ module Mutant
end
memoize :subject
class Finder
def self.run(root, &predicate)
new(root, predicate).match
end
private_class_method :new
attr_reader :match
private
def initialize(root, predicate)
@root, @predicate = root, predicate
test(root)
end
def test(node)
if @predicate.call(node)
@match = node
end
node.children.each do |child|
test(child) if child.kind_of?(Parser::AST::Node)
end
end
end
# Return matched node
#
# @return [Rubinus::AST::Node]
# @return [Parser::AST::Node]
# if node could be found
#
# @return [nil]
# otherwise
#
# @api private
#
def matched_node
last_match = nil
ast.walk do |predicate, node|
last_match = node if match?(node)
predicate
Finder.run(ast) do |node|
match?(node)
end
last_match
end
end # Method

View file

@ -16,6 +16,8 @@ module Mutant
end
memoize :identification
NAME_INDEX = 0
private
# Check if node is matched
@ -31,9 +33,11 @@ module Mutant
# @api private
#
def match?(node)
node.line == source_line &&
node.class == Rubinius::AST::Define &&
node.name == method_name
location = node.location || return
expression = location.expression || return
expression.line == source_line &&
node.type == :def &&
node.children[NAME_INDEX] == method_name
end
end # Instance

View file

@ -16,6 +16,10 @@ module Mutant
end
memoize :identification
RECEIVER_INDEX = 0
NAME_INDEX = 1
CONST_NAME_INDEX = 1
private
# Test for node match
@ -31,9 +35,8 @@ module Mutant
# @api private
#
def match?(node)
node.class == Rubinius::AST::DefineSingleton &&
line?(node) &&
name?(node) &&
line?(node) &&
name?(node) &&
receiver?(node)
end
@ -50,7 +53,8 @@ module Mutant
# @api private
#
def line?(node)
node.line == source_line
expression = node.location.expression || return
expression.line == source_line
end
# Test for name match
@ -66,7 +70,7 @@ module Mutant
# @api private
#
def name?(node)
node.body.name == method_name
node.children[NAME_INDEX] == method_name
end
# Test for receiver match
@ -82,11 +86,11 @@ module Mutant
# @api private
#
def receiver?(node)
receiver = node.receiver
case receiver
when Rubinius::AST::Self
receiver = node.children[RECEIVER_INDEX]
case receiver.type
when :self
true
when Rubinius::AST::ConstantAccess
when :const
receiver_name?(receiver)
else
$stderr.puts "Unable to find singleton method definition can only match receiver on Rubinius::AST::Self or Rubinius::AST::ConstantAccess, got #{receiver.class}"
@ -107,7 +111,8 @@ module Mutant
# @api private
#
def receiver_name?(node)
node.name.to_s == context.unqualified_name
name = node.children[CONST_NAME_INDEX]
name.to_s == context.unqualified_name
end
end # Singleton

View file

@ -0,0 +1,18 @@
module Mutant
# Mixin for node helpers
module NodeHelpers
# Build node
#
# @param [Symbol] type
#
# @return [Parser::AST::Node]
#
# @api private
#
def s(type, *children)
Parser::AST::Node.new(type, children)
end
end # NodeHelpers
end # Mutant

View file

@ -18,18 +18,18 @@ shared_examples_for 'a method matcher' do
end
it 'should have correct line number' do
(node.line - base).should eql(method_line)
(node.location.expression.line - base).should eql(method_line)
end
it 'should have correct arity' do
arguments.required.length.should eql(method_arity)
arguments.children.length.should eql(method_arity)
end
it 'should have correct scope in context' do
context.send(:scope).should eql(scope)
end
it 'should have the correct node class' do
node.should be_a(node_class)
it 'should have the correct node type' do
node.type.should be(type)
end
end

View file

@ -5,7 +5,7 @@ shared_examples_for 'a mutator' do
let(:object) { described_class }
unless instance_methods.map(&:to_s).include?('node')
let(:node) { source.to_ast }
let(:node) { parse(source) }
end
it_should_behave_like 'a command method'

View file

@ -8,6 +8,17 @@ $: << File.join(TestApp.root,'lib')
require 'test_app'
require 'mutant'
module ParserHelper
def generate(node)
Unparser.unparse(node)
end
def parse(string)
Parser::CurrentRuby.parse(string)
end
end
RSpec.configure do |config|
config.include(CompressHelper)
config.include(ParserHelper)
end

View file

@ -5,13 +5,13 @@ describe Mutant::Context::Scope, '#root' do
let(:object) { described_class.new(TestApp::Literal, path) }
let(:path) { mock('Path') }
let(:node) { ':node'.to_ast }
let(:node) { parse(':node') }
let(:scope) { subject.body }
let(:scope_body) { scope.body }
let(:expected_source) do
ToSource.to_source(<<-RUBY.to_ast)
generate(parse(<<-RUBY))
module TestApp
class Literal
:node
@ -21,11 +21,11 @@ describe Mutant::Context::Scope, '#root' do
end
let(:generated_source) do
ToSource.to_source(subject)
Unparser.unparse(subject)
end
let(:round_tripped_source) do
ToSource.to_source(expected_source.to_ast)
Unparser.unparse(parse(expected_source))
end
it 'should create correct source' do

View file

@ -26,7 +26,7 @@ describe Mutant::Loader::Eval, '.run' do
end
let(:node) do
source.to_ast
parse(source)
end
it 'should load nodes into vm' do
@ -36,6 +36,6 @@ describe Mutant::Loader::Eval, '.run' do
it 'should set file and line correctly' do
subject
::SomeNamespace::Bar.instance_method(:some_method).source_location.should eql(['test.rb', 3])
::SomeNamespace::Bar.instance_method(:some_method).source_location.should eql(['test.rb', 4])
end
end

View file

@ -14,16 +14,16 @@ describe Mutant::Matcher::Method::Instance, '#each' do
subject { object.each { |subject| yields << subject } }
let(:node_class) { Rubinius::AST::Define }
let(:method_name) { :bar }
let(:method_arity) { 0 }
let(:type) { :def }
let(:method_name) { :bar }
let(:method_arity) { 0 }
def name
node.name
node.children[0]
end
def arguments
node.arguments
node.children[1]
end
context 'when method is defined once' do

View file

@ -14,15 +14,15 @@ describe Mutant::Matcher::Method::Singleton, '#each' do
subject { object.each { |subject| yields << subject } }
let(:node_class) { Rubinius::AST::DefineSingleton }
let(:method_arity) { 0 }
let(:type) { :defs }
let(:method_arity) { 0 }
def name
node.body.name
node.children[1]
end
def arguments
node.body.arguments
node.children[2]
end
context 'on singleton methods' do

View file

@ -9,22 +9,20 @@ describe Mutant::Mutator, 'block' do
mutations = []
# Mutation of each statement in block
mutations << "foo\nself.bar".to_ast
mutations << "self.foo\nbar".to_ast
mutations << "foo\nself.bar"
mutations << "self.foo\nbar"
# Remove statement in block
mutations << Rubinius::AST::Block.new(1, ['self.foo'.to_ast])
mutations << Rubinius::AST::Block.new(1, ['self.bar'.to_ast])
mutations << Rubinius::AST::Block.new(1, ['nil'.to_ast])
mutations << 'self.foo'
mutations << 'self.bar'
mutations << 'nil'
end
it_should_behave_like 'a mutator'
end
context 'with one statement' do
let(:node) { Rubinius::AST::Block.new(1, ['self.foo'.to_ast]) }
let(:node) { 'self.foo' }
let(:mutations) do
mutations = []