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/hash'
require 'mutant/mutator/node/literal/regex' require 'mutant/mutator/node/literal/regex'
require 'mutant/mutator/node/literal/nil' require 'mutant/mutator/node/literal/nil'
require 'mutant/mutator/node/assignment'
require 'mutant/mutator/node/block' require 'mutant/mutator/node/block'
require 'mutant/mutator/node/while' require 'mutant/mutator/node/while'
require 'mutant/mutator/node/super' require 'mutant/mutator/node/super'

View file

@ -3,7 +3,7 @@ module Mutant
class Mutator class Mutator
# Abstract base class for node mutators # Abstract base class for node mutators
class Node < self class Node < self
include AbstractType include AbstractType, NodeHelpers
# Return identity of node # Return identity of node
# #
@ -14,7 +14,7 @@ module Mutant
# @api private # @api private
# #
def self.identity(node) def self.identity(node)
ToSource.to_source(node) Unparser.unparse(node)
end end
private private
@ -35,79 +35,55 @@ module Mutant
# #
alias_method :dup_node, :dup_input alias_method :dup_node, :dup_input
# Emit a new AST node # Return children
# #
# @param [Rubinis::AST::Node:Class] node_class # @return [Array<Parser::AST::Node>]
#
# @return [Parser::AST::Node]
# #
# @api private # @api private
# #
def emit_node(node_class, *arguments) def children
emit(new(node_class, *arguments)) node.children
end 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 # @api private
# #
def new_self(*arguments) def mutate_child(index, mutator = Mutator)
new(node.class, *arguments) children = node.children
child = children[index]
mutator.each(child) do |mutation|
emit_child_update(index, mutation)
end
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 # @api private
# #
def new_nil def emit_child_update(index, update)
new(Rubinius::AST::NilLiteral) new_children = children.dup
end new_children[index]=update
emit_self(new_children)
# 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)
end end
# Emit a new AST node with same class as wrapped node # Emit a new AST node with same class as wrapped node
# #
# @return [undefined] # @param [Array<Parser::AST::Node>] children
# #
# @api private # @api private
# #
def emit_self(*arguments) def emit_self(children)
emit(new_self(*arguments)) emit(Parser::AST::Node.new(node.type, children))
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
end end
# Emit a new AST node with NilLiteral class # Emit a new AST node with NilLiteral class
@ -117,44 +93,9 @@ module Mutant
# @api private # @api private
# #
def emit_nil def emit_nil
emit(new_nil) emit(s(:nil))
end 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 # Node
end # Mutator end # Mutator
end # Mutant end # Mutant

View file

@ -1,55 +1,60 @@
module Mutant module Mutant
class Mutator class Mutator
class Node class Node
# Abstract base class for assignment mutators
class Assignment < self class Assignment < self
class Variable < self
NAME_INDEX = 0
VALUE_INDEX = 1
private private
# Abstract base class for variable assignments # Perform dispatch
class Variable < self
# Emit mutants
# #
# @return [undefined] # @return [undefined]
# #
# @api private # @api private
# #
def dispatch def dispatch
emit_attribute_mutations(:name) do |mutation| emit_name_mutations
mutation.name = "#{self.class::PREFIX}#{mutation.name}".to_sym mutate_child(VALUE_INDEX)
mutation
end
emit_attribute_mutations(:value)
end end
# Mutator for local variable assignments # Emit name mutations
class Local < self #
PREFIX = ''.freeze # @return [undefined]
handle(:lvar) #
end # Local # @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 class Global < self
PREFIX = '$'.freeze PREFIX = '$'.freeze
handle(:gvar) handle(:gvasgn)
end # Global 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 # Variable
end # Assignment
end # Node end # Node
end # Mutator end # Mutator
end # Mutant end # Mutant

View file

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

View file

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

View file

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

View file

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