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/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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue