Port single variable assignment mutator to parser
This commit is contained in:
parent
23d93730af
commit
ededf82ebe
7 changed files with 81 additions and 131 deletions
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,55 +1,60 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class Node
|
||||
|
||||
# Abstract base class for assignment mutators
|
||||
class Assignment < self
|
||||
|
||||
private
|
||||
|
||||
# Abstract base class for variable assignments
|
||||
class Variable < self
|
||||
NAME_INDEX = 0
|
||||
VALUE_INDEX = 1
|
||||
|
||||
# Emit mutants
|
||||
private
|
||||
|
||||
# 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
|
||||
end # Variable
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
class Mutator
|
||||
class Util
|
||||
|
||||
# Mutators that mutates symbol inputs
|
||||
# Utility symbol mutator
|
||||
class Symbol < self
|
||||
|
||||
handle(::Symbol)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue