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.
|
||||
* Replace nil or add "do not touch me object" to literal mutations.
|
||||
* Add remaining literals
|
||||
* Mutate options for Regexp literals
|
||||
* Mutate options on Regexp literals
|
||||
* Move Mutant.random_* into Mutant::Random namespace.
|
||||
* 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(1)
|
||||
generator << new_self(-node.value)
|
||||
generator << new_self(Mutant.random_fixnum)
|
||||
generator.generate do
|
||||
new_self(Mutant.random_fixnum)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,9 @@ module Mutant
|
|||
generator << new_self(0.0)
|
||||
generator << new_self(1.0)
|
||||
generator << new_self(-node.value)
|
||||
generator << new_self(Mutant.random_float)
|
||||
generator.generate do
|
||||
new_self(Mutant.random_float)
|
||||
end
|
||||
generator << infinity
|
||||
generator << neg_infinity
|
||||
generator << nan
|
||||
|
|
|
@ -1,22 +1,94 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
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)
|
||||
return if same_node?(node)
|
||||
return self unless new?(node)
|
||||
@block.call(node)
|
||||
end
|
||||
|
||||
# FIXME: Use interhitable alias once in support gem.
|
||||
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
|
||||
|
||||
def same_node?(node)
|
||||
@sexp == node.to_sexp
|
||||
# Initialize generator
|
||||
#
|
||||
# @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
|
||||
|
|
|
@ -13,7 +13,9 @@ module Mutant
|
|||
#
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new_self(Mutant.random_hex_string)
|
||||
generator.generate do
|
||||
new_self(Mutant.random_hex_string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,9 @@ module Mutant
|
|||
#
|
||||
def mutants(generator)
|
||||
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
|
||||
|
|
|
@ -12,7 +12,7 @@ module Mutant
|
|||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
generator << new(Rubinius::AST::NilLiteral)
|
||||
generator << new_ni
|
||||
generator << new(Rubinius::AST::FalseLiteral)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -93,6 +93,21 @@ describe Mutant::Mutator, '#each' do
|
|||
it_should_behave_like 'a mutation enumerator method'
|
||||
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
|
||||
let(:source) { '10' }
|
||||
|
||||
|
@ -235,6 +250,32 @@ describe Mutant::Mutator, '#each' do
|
|||
it_should_behave_like 'a mutation enumerator method'
|
||||
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
|
||||
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