Port single variable assignment mutator to parser

This commit is contained in:
Markus Schirp 2013-06-04 20:23:06 +02:00
parent 23d93730af
commit ededf82ebe
7 changed files with 81 additions and 131 deletions

View file

@ -51,6 +51,7 @@ require 'mutant/mutator/node/literal/array'
require 'mutant/mutator/node/literal/hash'
require 'mutant/mutator/node/literal/regex'
require 'mutant/mutator/node/literal/nil'
require 'mutant/mutator/node/assignment'
require 'mutant/mutator/node/block'
require 'mutant/mutator/node/while'
require 'mutant/mutator/node/super'

View file

@ -3,7 +3,7 @@ module Mutant
class Mutator
# Abstract base class for node mutators
class Node < self
include AbstractType
include AbstractType, NodeHelpers
# Return identity of node
#
@ -14,7 +14,7 @@ module Mutant
# @api private
#
def self.identity(node)
ToSource.to_source(node)
Unparser.unparse(node)
end
private
@ -35,79 +35,55 @@ module Mutant
#
alias_method :dup_node, :dup_input
# Emit a new AST node
# Return children
#
# @param [Rubinis::AST::Node:Class] node_class
#
# @return [Parser::AST::Node]
# @return [Array<Parser::AST::Node>]
#
# @api private
#
def emit_node(node_class, *arguments)
emit(new(node_class, *arguments))
def children
node.children
end
# Create a new AST node with same class as wrapped node
# Dispatch on child index
#
# @return [Parser::AST::Node]
# @param [Fixnum] index
#
# @return [undefined]
#
# @api private
#
def new_self(*arguments)
new(node.class, *arguments)
def mutate_child(index, mutator = Mutator)
children = node.children
child = children[index]
mutator.each(child) do |mutation|
emit_child_update(index, mutation)
end
end
# Create a new AST node with Rubnius::AST::NilLiteral class
# Emit updated child
#
# @return [Parse::AST::Node]
# @param [Fixnum] index
# @param [Object] update
#
# @return [undefined]
#
# @api private
#
def new_nil
new(Rubinius::AST::NilLiteral)
end
# Create a new AST node with the same line as wrapped node
#
# @param [Class:Rubinius::AST::Node] node_class
#
# @return [Rubinius::AST::Node]
#
# @api private
#
def new(node_class, *arguments)
node_class.new(node.line, *arguments)
def emit_child_update(index, update)
new_children = children.dup
new_children[index]=update
emit_self(new_children)
end
# Emit a new AST node with same class as wrapped node
#
# @return [undefined]
# @param [Array<Parser::AST::Node>] children
#
# @api private
#
def emit_self(*arguments)
emit(new_self(*arguments))
end
# Emit body mutations
#
# @param [Symbol] name
#
# @return [undefined]
#
# @api private
#
def emit_attribute_mutations(name, mutator = Mutator)
value = node.public_send(name)
mutator.each(value) do |mutation|
dup = dup_node
dup.public_send(:"#{name}=", mutation)
if block_given?
dup = yield(dup)
end
emit(dup)
end
def emit_self(children)
emit(Parser::AST::Node.new(node.type, children))
end
# Emit a new AST node with NilLiteral class
@ -117,44 +93,9 @@ module Mutant
# @api private
#
def emit_nil
emit(new_nil)
emit(s(:nil))
end
# Return new Rubiinius::AST::SendWithArguments node
#
# @param [Rubnius::AST::Node] receiver
# @param [Symbol] name
# @param [Object] arguments
#
# @return [Rubinius::AST::SendWithArguments]
#
# @api private
#
def new_send_with_arguments(receiver, name, arguments)
new(Rubinius::AST::SendWithArguments, receiver, name, arguments)
end
# Return AST representing send
#
# @param [Rubinius::AST::Node] receiver
# @param [Symbol] name
#
# @return [Rubnius::AST::Send]
#
# @api private
#
def new_send(receiver, name)
new(Rubinius::AST::Send, receiver, name)
end
# Return duplicated (unfrozen) node each call
#
# @return [Rubinius::AST::Node]
#
# @api private
#
alias_method :dup_node, :dup_input
end # Node
end # Mutator
end # Mutant

View file

@ -1,55 +1,60 @@
module Mutant
class Mutator
class Node
# Abstract base class for assignment mutators
class Assignment < self
class Variable < self
NAME_INDEX = 0
VALUE_INDEX = 1
private
# Abstract base class for variable assignments
class Variable < self
# Emit mutants
# Perform dispatch
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_attribute_mutations(:name) do |mutation|
mutation.name = "#{self.class::PREFIX}#{mutation.name}".to_sym
mutation
end
emit_attribute_mutations(:value)
emit_name_mutations
mutate_child(VALUE_INDEX)
end
# Mutator for local variable assignments
class Local < self
PREFIX = ''.freeze
handle(:lvar)
end # Local
# Emit name mutations
#
# @return [undefined]
#
# @api private
#
def emit_name_mutations
name = children[NAME_INDEX]
Mutator::Util::Symbol.each(name) do |name|
emit_child_update(NAME_INDEX, "#{self.class::PREFIX}#{name}")
end
end
# Mutator for instance variable assignments
class Instance < self
PREFIX = '@'.freeze
handle(:ivar)
end # Instance
# Mutator for class variable assignments
class Class < self
PREFIX = '@@'.freeze
handle(:cvar)
end # Class
# Mutator for global variable assignments
class Global < self
PREFIX = '$'.freeze
handle(:gvar)
handle(:gvasgn)
end # Global
end # Access
class Class < self
PREFIX = '@@'.freeze
handle(:cvasgn)
end # Class
class Instance < self
PREFIX = '@'.freeze
handle(:ivasgn)
end # Instance
class Local < self
PREFIX = ''.freeze
handle(:lvasgn)
end # Local
end # Variable
end # Assignment
end # Node
end # Mutator
end # Mutant

View file

@ -8,7 +8,7 @@ module Mutant
handle(
:self, :zsuper, :not, :or, :and, :defined,
:next, :break, :match, :gvar, :cvar, :ensure, :rescue,
:dstr, :dsym, :yield, :begin, :rescue
:dstr, :dsym, :yield, :begin, :rescue, :gvasgn
)
private

View file

@ -23,6 +23,8 @@ module Mutant
self
end
private
# Test if mutation is new
#
# @param [Object] generated
@ -38,6 +40,7 @@ module Mutant
def new?(generated)
input != generated
end
end
end
end
end # Util
end # Mutator
end # Mutant

View file

@ -2,7 +2,7 @@ module Mutant
class Mutator
class Util
# Mutators that mutates symbol inputs
# Utility symbol mutator
class Symbol < self
handle(::Symbol)

View file

@ -16,9 +16,9 @@ shared_examples_for 'a mutator' do
it { should be_instance_of(to_enum.class) }
def assert_transitive(ast)
generated = ToSource.to_source(ast)
parsed = generated.to_ast
again = ToSource.to_source(parsed)
generated = generate(ast)
parsed = parse(generated)
again = generate(parsed)
unless generated == again
fail "Untransitive:\n%s\n---\n%s" % [generated, again]
end
@ -29,7 +29,7 @@ shared_examples_for 'a mutator' do
mutations.map do |mutation|
case mutation
when String
ast = mutation.to_ast
ast = parse(mutation)
assert_transitive(ast)
ast
when Rubinius::AST::Node
@ -39,13 +39,13 @@ shared_examples_for 'a mutator' do
raise
end
end.map do |node|
ToSource.to_source(node)
generate(node)
end.to_set
end
end
it 'generates the expected mutations' do
generated = self.subject.map { |mutation| ToSource.to_source(mutation) }.to_set
generated = self.subject.map { |mutation| generate(mutation) }.to_set
missing = (expected_mutations - generated).to_a
unexpected = (generated - expected_mutations).to_a