diff --git a/TODO b/TODO index 49092d18..6535f9ab 100644 --- a/TODO +++ b/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. diff --git a/lib/mutant/mutator/fixnum_literal.rb b/lib/mutant/mutator/fixnum_literal.rb index d336db7e..9b9c9116 100644 --- a/lib/mutant/mutator/fixnum_literal.rb +++ b/lib/mutant/mutator/fixnum_literal.rb @@ -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 diff --git a/lib/mutant/mutator/float_literal.rb b/lib/mutant/mutator/float_literal.rb index a50f8b86..a0a47aa0 100644 --- a/lib/mutant/mutator/float_literal.rb +++ b/lib/mutant/mutator/float_literal.rb @@ -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 diff --git a/lib/mutant/mutator/generator.rb b/lib/mutant/mutator/generator.rb index 0b0fea6c..706a800f 100644 --- a/lib/mutant/mutator/generator.rb +++ b/lib/mutant/mutator/generator.rb @@ -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 diff --git a/lib/mutant/mutator/string_literal.rb b/lib/mutant/mutator/string_literal.rb index d9bd240d..2ab6601b 100644 --- a/lib/mutant/mutator/string_literal.rb +++ b/lib/mutant/mutator/string_literal.rb @@ -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 diff --git a/lib/mutant/mutator/symbol_literal.rb b/lib/mutant/mutator/symbol_literal.rb index 8002ec09..e8a9dde3 100644 --- a/lib/mutant/mutator/symbol_literal.rb +++ b/lib/mutant/mutator/symbol_literal.rb @@ -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 diff --git a/lib/mutant/mutator/true_literal.rb b/lib/mutant/mutator/true_literal.rb index ec116b19..2098a9ae 100644 --- a/lib/mutant/mutator/true_literal.rb +++ b/lib/mutant/mutator/true_literal.rb @@ -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 diff --git a/spec/unit/mutant/mutator/each_spec.rb b/spec/unit/mutant/mutator/each_spec.rb index 313bf478..aef48bff 100644 --- a/spec/unit/mutant/mutator/each_spec.rb +++ b/spec/unit/mutant/mutator/each_spec.rb @@ -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" } diff --git a/spec/unit/mutant/mutator/generator/generate_spec.rb b/spec/unit/mutant/mutator/generator/generate_spec.rb new file mode 100644 index 00000000..92e14e54 --- /dev/null +++ b/spec/unit/mutant/mutator/generator/generate_spec.rb @@ -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