Handle binary operator methods correctly

This commit is contained in:
Markus Schirp 2013-01-03 19:09:43 +01:00
parent c6e60d9178
commit 5565f5309a
6 changed files with 141 additions and 34 deletions

View file

@ -1,3 +1,10 @@
# v0.2.11 2013-01-03
* [change] Handle binary operator methods in dedicated mutator
* [fixed] Do not crash when mutating binary operator method
[Compare v0.2.10..v0.2.11](https://github.com/mbj/mutant/compare/v0.2.10...v0.2.11)
# v0.2.10 2013-01-03
* [fixed] Do not mutate receivers away when message name is a keyword

4
TODO
View file

@ -1,11 +1,13 @@
Code:
* Test mutant with dynamically created zombie.
* Fix ugly code within default parameters
* Fix ugly code within default parameter mutations
* Break up lib/mutant/mutator/node/send.rb in class specific files
AST:
* Fix the rubinius AST to allow setting @vcall_style variable in Rubinius::AST::Send nodes.
Mutations:
* Add binary operator specific mutations (YAY, finally reached this point)
* Add some kind of a "do not touch me object" that raises on all messages.
It can be used to make sure each literal value is touched.
* Replace nil or add "do not touch me object" to literal mutations.

View file

@ -31,14 +31,51 @@ end
module Mutant
# The list of ruby kewords from http://ruby-doc.org/docs/keywords/1.9/
KEYWORDS = IceNine.deep_freeze(%W(
KEYWORDS = %w(
BEGIN END __ENCODING__ __END__ __FILE__
__LINE__ alias and begin break case class
def define do else elsif end ensure false
for if in module next nil not or redo
rescue retry return self super then true
undef unless until when while yield
).map(&:to_sym).to_set)
).map(&:to_sym).to_set.freeze
BINARY_METHOD_OPERATOR_EXPANSIONS = {
:<=> => :spaceship_operator,
:=== => :case_equality_operator,
:[]= => :element_writer,
:[] => :element_reader,
:<= => :less_than_or_equal_to_operator,
:>= => :greater_than_or_equal_to_operator,
:== => :equality_operator,
:'!~' => :nomatch_operator,
:'!=' => :inequality_operator,
:=~ => :match_operator,
:<< => :left_shift_operator,
:>> => :right_shift_operator,
:** => :exponentation_operator,
:* => :multiplication_operator,
:% => :modulo_operator,
:/ => :division_operator,
:| => :bitwise_or_operator,
:^ => :bitwise_xor_operator,
:& => :bitwise_and_operator,
:< => :less_than_operator,
:> => :greater_than_operator,
:+ => :addition_operator,
:- => :substraction_operator
}.freeze
UNARY_METHOD_OPERATOR_EXPANSIONS = {
:~@ => :unary_match_operator,
:+@ => :unary_addition_operator,
:-@ => :unary_substraction_operator,
:'!' => :negation_operator
}.freeze
BINARY_METHOD_OPERATORS = BINARY_METHOD_OPERATOR_EXPANSIONS.keys.to_set.freeze
OPERATOR_EXPANSIONS = BINARY_METHOD_OPERATOR_EXPANSIONS.merge(UNARY_METHOD_OPERATOR_EXPANSIONS).freeze
# Define instance of subclassed superclass as constant
#

View file

@ -163,6 +163,48 @@ module Mutant
handle(Rubinius::AST::SendWithArguments)
class BinaryOperatorMethod < Node
private
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_left_mutations
emit_right_mutations
end
# Emit left mutations
#
# @return [undefined]
#
# @api private
#
def emit_left_mutations
emit_attribute_mutations(:receiver)
end
# Emit right mutations
#
# @return [undefined]
#
# @api private
#
def emit_right_mutations
right = node.arguments.array.first
Mutator.each(right).each do |mutated|
dup = dup_node
dup.arguments.array[0] = mutated
emit(dup)
end
end
end
private
# Emit mutations
@ -174,6 +216,37 @@ module Mutant
def dispatch
super
emit_call_remove_mutation
emit_argument_mutations
end
# Test if message is a binary operator
#
# @return [true]
# if message is a binary operator
#
# @return [false]
# otherwise
#
# @api private
#
def binary_operator?
Mutant::BINARY_METHOD_OPERATORS.include?(node.name)
end
# Emit argument mutations
#
# @api private
#
# @return [undefined]
#
# @api private
#
def emit_argument_mutations
if binary_operator?
run(BinaryOperatorMethod)
return
end
emit_attribute_mutations(:arguments)
end

View file

@ -56,36 +56,6 @@ module Mutant
mutation.subject.matcher
end
MAPPING = {
:<=> => :spaceship_operator,
:=== => :case_equality_operator,
:[]= => :element_writer,
:[] => :element_reader,
:<= => :less_than_or_equal_to_operator,
:>= => :greater_than_or_equal_to_operator,
:~@ => :unary_match_operator,
:+@ => :unary_addition_operator,
:-@ => :unary_substraction_operator,
:== => :equality_operator,
:'!~' => :nomatch_operator,
:'!=' => :inequality_operator,
:=~ => :match_operator,
:<< => :left_shift_operator,
:>> => :right_shift_operator,
:** => :exponentation_operator,
:* => :multiplication_operator,
:% => :modulo_operator,
:/ => :division_operator,
:| => :bitwise_or_operator,
:'!' => :negation_operator,
:^ => :bitwise_xor_operator,
:& => :bitwise_and_operator,
:< => :less_than_operator,
:> => :greater_than_operator,
:+ => :addition_operator,
:- => :substraction_operator
}
EXPANSIONS = {
/\?\z/ => '_predicate',
/=\z/ => '_writer',
@ -114,7 +84,7 @@ module Mutant
# @api private
#
def mapped_name
MAPPING[method_name]
OPERATOR_EXPANSIONS[method_name]
end
# Return expanded name

View file

@ -102,6 +102,24 @@ describe Mutant::Mutator, 'send' do
end
end
context 'binary operator methods' do
Mutant::BINARY_METHOD_OPERATORS.each do |operator|
let(:source) { "true #{operator} false" }
let(:mutations) do
mutations = []
mutations << "((false) #{operator} (false))"
mutations << "((nil) #{operator} (false))"
mutations << "((true) #{operator} (true))"
mutations << "((true) #{operator} (nil))"
mutations << 'true'
mutations << 'false'
end
it_should_behave_like 'a mutator'
end
end
context 'two arguments' do
let(:source) { 'foo(nil, nil)' }