Retry on random mutates that are the same
* This fixes problems where generated mutations have a chance to be the same like the mutation subject.
This commit is contained in:
parent
2b7e9c60c2
commit
02a726d767
9 changed files with 176 additions and 12 deletions
2
TODO
2
TODO
|
@ -17,6 +17,6 @@
|
||||||
It can be used to make sure each literal value is touched.
|
It can be used to make sure each literal value is touched.
|
||||||
* Replace nil or add "do not touch me object" to literal mutations.
|
* Replace nil or add "do not touch me object" to literal mutations.
|
||||||
* Add remaining literals
|
* Add remaining literals
|
||||||
* Mutate options for Regexp literals
|
* Mutate options on Regexp literals
|
||||||
* Move Mutant.random_* into Mutant::Random namespace.
|
* Move Mutant.random_* into Mutant::Random namespace.
|
||||||
* Use inheritable alias once (virtus,veritas,mapper,session, ...) support gem is born.
|
* Use inheritable alias once (virtus,veritas,mapper,session, ...) support gem is born.
|
||||||
|
|
|
@ -14,7 +14,9 @@ module Mutant
|
||||||
generator << new_self(0)
|
generator << new_self(0)
|
||||||
generator << new_self(1)
|
generator << new_self(1)
|
||||||
generator << new_self(-node.value)
|
generator << new_self(-node.value)
|
||||||
generator << new_self(Mutant.random_fixnum)
|
generator.generate do
|
||||||
|
new_self(Mutant.random_fixnum)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,9 @@ module Mutant
|
||||||
generator << new_self(0.0)
|
generator << new_self(0.0)
|
||||||
generator << new_self(1.0)
|
generator << new_self(1.0)
|
||||||
generator << new_self(-node.value)
|
generator << new_self(-node.value)
|
||||||
generator << new_self(Mutant.random_float)
|
generator.generate do
|
||||||
|
new_self(Mutant.random_float)
|
||||||
|
end
|
||||||
generator << infinity
|
generator << infinity
|
||||||
generator << neg_infinity
|
generator << neg_infinity
|
||||||
generator << nan
|
generator << nan
|
||||||
|
|
|
@ -1,22 +1,94 @@
|
||||||
module Mutant
|
module Mutant
|
||||||
class Mutator
|
class Mutator
|
||||||
class Generator
|
class Generator
|
||||||
def initialize(node,block)
|
|
||||||
@sexp,@block = node.to_sexp,block
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# Append node to generated mutations if node does not equal orignal node
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
def append(node)
|
def append(node)
|
||||||
return if same_node?(node)
|
return self unless new?(node)
|
||||||
@block.call(node)
|
@block.call(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
# FIXME: Use interhitable alias once in support gem.
|
# FIXME: Use interhitable alias once in support gem.
|
||||||
alias :<< :append
|
alias :<< :append
|
||||||
|
|
||||||
|
# Maximum amount of tries to generate a new node
|
||||||
|
MAX_TRIES = 3
|
||||||
|
|
||||||
|
# Call block until it generates a new AST node
|
||||||
|
#
|
||||||
|
# The primary use of this method is to give the random generated AST nodes
|
||||||
|
# a nice interface for retring generation when generation accidentally generated the
|
||||||
|
# same AST that is about to be mutated.
|
||||||
|
#
|
||||||
|
# @yield
|
||||||
|
# Execute block until AST node that does not equal wrapped node is generated by block
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @raise [RuntimeError]
|
||||||
|
# raises RuntimeError in case no new ast node can be generated after MAX_TRIES.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def generate
|
||||||
|
MAX_TRIES.times do
|
||||||
|
node = yield
|
||||||
|
return call(node) if new?(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "New AST could not be generated after #{MAX_TRIES} attempts"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def same_node?(node)
|
# Initialize generator
|
||||||
@sexp == node.to_sexp
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
# @param [#call(node)] block
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def initialize(node,block)
|
||||||
|
@sexp,@block = node.to_sexp,block
|
||||||
|
end
|
||||||
|
|
||||||
|
# Call block with node
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def call(node)
|
||||||
|
@block.call(node)
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if node does not equal original node
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# returns true when node is differend from the node to be mutated
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# returns false otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def new?(node)
|
||||||
|
@sexp != node.to_sexp
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,9 @@ module Mutant
|
||||||
#
|
#
|
||||||
def mutants(generator)
|
def mutants(generator)
|
||||||
generator << new_nil
|
generator << new_nil
|
||||||
generator << new_self(Mutant.random_hex_string)
|
generator.generate do
|
||||||
|
new_self(Mutant.random_hex_string)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,9 @@ module Mutant
|
||||||
#
|
#
|
||||||
def mutants(generator)
|
def mutants(generator)
|
||||||
generator << new_nil
|
generator << new_nil
|
||||||
generator << new(Rubinius::AST::SymbolLiteral,Mutant.random_hex_string.to_sym)
|
generator.generate do
|
||||||
|
new_self(Mutant.random_hex_string.to_sym)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ module Mutant
|
||||||
# @return [undefined]
|
# @return [undefined]
|
||||||
#
|
#
|
||||||
def mutants(generator)
|
def mutants(generator)
|
||||||
generator << new(Rubinius::AST::NilLiteral)
|
generator << new_ni
|
||||||
generator << new(Rubinius::AST::FalseLiteral)
|
generator << new(Rubinius::AST::FalseLiteral)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -93,6 +93,21 @@ describe Mutant::Mutator, '#each' do
|
||||||
it_should_behave_like 'a mutation enumerator method'
|
it_should_behave_like 'a mutation enumerator method'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'interpolated string literal (DynamicString)' do
|
||||||
|
let(:source) { '"foo#{1}bar"' }
|
||||||
|
|
||||||
|
let(:mutations) do
|
||||||
|
mutations = []
|
||||||
|
mutations << 'nil'
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Mutant.stub(:random_hex_string => random_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a mutation enumerator method'
|
||||||
|
end
|
||||||
|
|
||||||
context 'fixnum literal' do
|
context 'fixnum literal' do
|
||||||
let(:source) { '10' }
|
let(:source) { '10' }
|
||||||
|
|
||||||
|
@ -235,6 +250,32 @@ describe Mutant::Mutator, '#each' do
|
||||||
it_should_behave_like 'a mutation enumerator method'
|
it_should_behave_like 'a mutation enumerator method'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'regexp literal' do
|
||||||
|
let(:source) { '/foo/' }
|
||||||
|
|
||||||
|
let(:base_mutations) do
|
||||||
|
mutations = []
|
||||||
|
mutations << 'nil'
|
||||||
|
mutations << "/#{random_string}/"
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Mutant.stub(:random_hex_string => random_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:mutations) { base_mutations }
|
||||||
|
|
||||||
|
it_should_behave_like 'a mutation enumerator method'
|
||||||
|
|
||||||
|
#it 'when source is empty regexp' do
|
||||||
|
# let(:source) { '//' }
|
||||||
|
|
||||||
|
# let(:mutations) { base_mutations - [source.to_ast] }
|
||||||
|
|
||||||
|
# it_should_behave_like 'a mutation enumerator method'
|
||||||
|
#end
|
||||||
|
end
|
||||||
|
|
||||||
context 'block literal' do
|
context 'block literal' do
|
||||||
let(:source) { "true\nfalse" }
|
let(:source) { "true\nfalse" }
|
||||||
|
|
||||||
|
|
43
spec/unit/mutant/mutator/generator/generate_spec.rb
Normal file
43
spec/unit/mutant/mutator/generator/generate_spec.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Mutator::Generator,'#generate' do
|
||||||
|
subject { object.generate { node } }
|
||||||
|
|
||||||
|
class Block
|
||||||
|
attr_reader :arguments
|
||||||
|
|
||||||
|
def called?
|
||||||
|
defined?(@arguments)
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(*arguments)
|
||||||
|
@arguments = arguments
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:object) { described_class.new(wrapped_node,block) }
|
||||||
|
let(:block) { Block.new }
|
||||||
|
let(:wrapped_node) { '"foo"'.to_ast }
|
||||||
|
|
||||||
|
context 'when new AST is generated' do
|
||||||
|
let(:node) { '"bar"'.to_ast }
|
||||||
|
|
||||||
|
it 'should call block' do
|
||||||
|
subject
|
||||||
|
block.should be_called
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should call block with node' do
|
||||||
|
subject
|
||||||
|
block.arguments.should eql([node])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when new AST could not be generated' do
|
||||||
|
let(:node) { '"foo"'.to_ast }
|
||||||
|
|
||||||
|
it 'should raise error' do
|
||||||
|
expect { subject }.to raise_error(RuntimeError,'New AST could not be generated after 3 attempts')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue