Add mutation for some literals
* This is in progress code. The plan is to support all literals before beginning to cleanup and dedup the mutation generation. Have to understand the AST and the possible mutations more in depth before making structural decisions here.
This commit is contained in:
parent
a8e635d77a
commit
9e8b451933
16 changed files with 666 additions and 15 deletions
6
TODO
6
TODO
|
@ -1,4 +1,3 @@
|
|||
* Add a nice way to access the root ast to place the mutated ast nodes into.
|
||||
* Get a rid of heckle and test mutant with mutant.
|
||||
This is interesting IMHO mutant should have another entry point
|
||||
that does not create the ::Mutant namespace, ideas:
|
||||
|
@ -13,3 +12,8 @@
|
|||
* Allow matches on attr_reader with literal name argument(s)?
|
||||
* Allow matches on define_method with literal name argument?
|
||||
* Make sure file is set in generated asts. Has a stupid default corrently.
|
||||
* Remove duplicated code in Mutatand::Mutator::*
|
||||
* 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.
|
||||
* Add remaining literals
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# For Veritas::Immutable. will be extracted soon
|
||||
require 'veritas'
|
||||
|
||||
require 'securerandom'
|
||||
|
||||
# Library namespace
|
||||
module Mutant
|
||||
# Helper method for raising not implemented exceptions
|
||||
|
@ -47,9 +49,49 @@ module Mutant
|
|||
end
|
||||
|
||||
private_class_method :not_implemented_info
|
||||
|
||||
# Return random string
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.random_hex_string
|
||||
SecureRandom.hex(10)
|
||||
end
|
||||
|
||||
# Return random fixnum
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.random_fixnum
|
||||
Random.rand(1000)
|
||||
end
|
||||
|
||||
# Return random float
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.random_fixnum
|
||||
Random.rand
|
||||
end
|
||||
end
|
||||
|
||||
require 'mutant/mutator'
|
||||
require 'mutant/mutator/true_literal'
|
||||
require 'mutant/mutator/false_literal'
|
||||
require 'mutant/mutator/symbol_literal'
|
||||
require 'mutant/mutator/string_literal'
|
||||
require 'mutant/mutator/fixnum_literal'
|
||||
require 'mutant/mutator/float_literal'
|
||||
require 'mutant/mutator/array_literal'
|
||||
require 'mutant/mutator/empty_array'
|
||||
require 'mutant/mutator/hash_literal'
|
||||
require 'mutant/mutator/block'
|
||||
require 'mutant/loader'
|
||||
require 'mutant/context'
|
||||
require 'mutant/context/constant'
|
||||
|
|
|
@ -31,7 +31,7 @@ module Mutant
|
|||
#
|
||||
def each(&block)
|
||||
return to_enum(__method__) unless block_given?
|
||||
Mutator.new(node).each(&block)
|
||||
Mutator.build(node).each(&block)
|
||||
|
||||
self
|
||||
end
|
||||
|
|
|
@ -1,7 +1,69 @@
|
|||
module Mutant
|
||||
# Mutate rubinius AST nodes
|
||||
class Mutator
|
||||
# Initialize mutator
|
||||
include Enumerable, Veritas::Immutable
|
||||
|
||||
# Build mutation node
|
||||
#
|
||||
# @param [Rubinius::AST::Node] node
|
||||
#
|
||||
# @return [Mutator]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(node)
|
||||
mutator(node).new(node)
|
||||
end
|
||||
|
||||
# Return mutator for node or raise
|
||||
#
|
||||
# @param [Rubinius::AST::Node] node
|
||||
#
|
||||
# @return [Mutator]
|
||||
#
|
||||
# @raise [ArgumentError]
|
||||
# raises ArgumentError if mutator for node cannot be found
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.mutator(node)
|
||||
unqualified_name = node.class.name.split('::').last
|
||||
self.const_get(unqualified_name)
|
||||
end
|
||||
|
||||
class Generator
|
||||
def initialize(block)
|
||||
@block = block
|
||||
end
|
||||
|
||||
def append(node)
|
||||
@block.call(node)
|
||||
end
|
||||
|
||||
alias :<< :append
|
||||
end
|
||||
|
||||
# Enumerate mutated asts
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def each(&block)
|
||||
return to_enum(__method__) unless block_given?
|
||||
mutants(Generator.new(block))
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return wrapped node
|
||||
#
|
||||
# @return [Rubinius::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :node
|
||||
|
||||
# Initialize mutator with
|
||||
#
|
||||
# @param [Rubinius::AST::Node] node
|
||||
#
|
||||
|
@ -11,13 +73,48 @@ module Mutant
|
|||
@node = node
|
||||
end
|
||||
|
||||
# # Enumerate mutated asts
|
||||
# #
|
||||
# # @api private
|
||||
# #
|
||||
# def each
|
||||
# return to_enum(__method__) unless block_given?
|
||||
# self
|
||||
# end
|
||||
# Create a new AST node
|
||||
#
|
||||
# @param [Rubinis::AST::Node:Class] node_class
|
||||
#
|
||||
# @return [Rubinius::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def new(node_class,*arguments)
|
||||
node_class.new(node.line,*arguments)
|
||||
end
|
||||
|
||||
# Create a new AST node with same class as wrapped node
|
||||
#
|
||||
# @return [Rubinius::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def new_self(*arguments)
|
||||
new(node.class,*arguments)
|
||||
end
|
||||
|
||||
# Create a new AST node with NilLiteral class
|
||||
#
|
||||
# @return [Rubinius::AST::NilLiteral]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def new_nil
|
||||
new(Rubinius::AST::NilLiteral)
|
||||
end
|
||||
|
||||
# Append mutations
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# @param [#<<] generator
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
Mutant.not_implemented(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
38
lib/mutant/mutator/array_literal.rb
Normal file
38
lib/mutant/mutator/array_literal.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class ArrayLiteral < Mutator
|
||||
|
||||
private
|
||||
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new_self([])
|
||||
generator << new_self(dup_body << new_nil)
|
||||
mutate_elements(generator)
|
||||
mutate_element_presence(generator)
|
||||
end
|
||||
|
||||
def dup_body
|
||||
node.body.dup
|
||||
end
|
||||
|
||||
def mutate_element_presence(generator)
|
||||
node.body.each_with_index do |child,index|
|
||||
body = dup_body
|
||||
body.delete_at(index)
|
||||
generator << new_self(body)
|
||||
end
|
||||
end
|
||||
|
||||
def mutate_elements(generator)
|
||||
node.body.each_with_index do |child,index|
|
||||
body = dup_body
|
||||
Mutator.build(child).each do |mutation|
|
||||
body[index]=mutation
|
||||
generator << new_self(body)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
39
lib/mutant/mutator/block.rb
Normal file
39
lib/mutant/mutator/block.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class Block < Mutator
|
||||
|
||||
private
|
||||
|
||||
def mutants(generator)
|
||||
mutate_elements(generator)
|
||||
mutate_presence(generator)
|
||||
end
|
||||
|
||||
def array
|
||||
node.array
|
||||
end
|
||||
|
||||
def dup_array
|
||||
array.dup
|
||||
end
|
||||
|
||||
def mutate_presence(generator)
|
||||
array.each_index do |index|
|
||||
array = dup_array
|
||||
array.delete_at(index)
|
||||
generator << new_self(array)
|
||||
end
|
||||
end
|
||||
|
||||
def mutate_elements(generator)
|
||||
array.each_with_index do |child,index|
|
||||
array = dup_array
|
||||
Mutator.build(child).each do |mutation|
|
||||
array[index]=mutation
|
||||
generator << new_self(array)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
lib/mutant/mutator/empty_array.rb
Normal file
13
lib/mutant/mutator/empty_array.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class EmptyArray < Mutator
|
||||
|
||||
private
|
||||
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new(Rubinius::AST::ArrayLiteral,[new_nil])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
lib/mutant/mutator/false_literal.rb
Normal file
20
lib/mutant/mutator/false_literal.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
# Represent mutations of false literal
|
||||
class FalseLiteral < Mutator
|
||||
|
||||
private
|
||||
|
||||
# Append mutants
|
||||
#
|
||||
# @param [#<<] generator
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new(Rubinius::AST::TrueLiteral)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/mutant/mutator/fixnum_literal.rb
Normal file
21
lib/mutant/mutator/fixnum_literal.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
# Represent mutations on fixnum literal
|
||||
class FixnumLiteral < Mutator
|
||||
private
|
||||
# Append mutants
|
||||
#
|
||||
# @param [#<<] generator
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
generator << new(Rubinius::AST::NilLiteral)
|
||||
generator << new_self(0)
|
||||
generator << new_self(1)
|
||||
generator << new_self(-node.value)
|
||||
generator << new_self(Mutant.random_fixnum)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
lib/mutant/mutator/float_literal.rb
Normal file
47
lib/mutant/mutator/float_literal.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
# Represent mutations on fixnum literal
|
||||
class FloatLiteral < Mutator
|
||||
private
|
||||
# Append mutants
|
||||
#
|
||||
# @param [#<<] generator
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new_self(0.0)
|
||||
generator << new_self(1.0)
|
||||
generator << new_self(-node.value)
|
||||
generator << new_self(Mutant.random_float)
|
||||
generator << infinity
|
||||
generator << nan
|
||||
end
|
||||
|
||||
# Return AST representing infinity
|
||||
#
|
||||
# @return [Rubinius::Node::AST]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def infinity
|
||||
'0.0/0.0'.to_ast.tap do |call|
|
||||
call.line = node.line
|
||||
end
|
||||
end
|
||||
|
||||
# Return AST representing NaN
|
||||
#
|
||||
# @return [Rubinius::Node::AST]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def nan
|
||||
'1.0/0.0'.to_ast.tap do |call|
|
||||
call.line = node.line
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
46
lib/mutant/mutator/hash_literal.rb
Normal file
46
lib/mutant/mutator/hash_literal.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class HashLiteral < Mutator
|
||||
|
||||
private
|
||||
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new_self([])
|
||||
generator << new_self(array + [new_nil, new_nil])
|
||||
mutate_elements(generator)
|
||||
mutate_presence(generator)
|
||||
end
|
||||
|
||||
def array
|
||||
node.array
|
||||
end
|
||||
|
||||
def dup_array
|
||||
array.dup
|
||||
end
|
||||
|
||||
def dup_pairs
|
||||
end
|
||||
|
||||
def mutate_presence(generator)
|
||||
pairs = array.each_slice(2).to_a
|
||||
pairs.each_index do |index|
|
||||
dup_pairs = pairs.dup
|
||||
dup_pairs.delete_at(index)
|
||||
generator << new_self(dup_pairs.flatten)
|
||||
end
|
||||
end
|
||||
|
||||
def mutate_elements(generator)
|
||||
array.each_with_index do |child,index|
|
||||
array = dup_array
|
||||
Mutator.build(child).each do |mutation|
|
||||
array[index]=mutation
|
||||
generator << new_self(array)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
lib/mutant/mutator/string_literal.rb
Normal file
20
lib/mutant/mutator/string_literal.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
# Represent mutations on string literal
|
||||
class StringLiteral < Mutator
|
||||
|
||||
private
|
||||
|
||||
# Append mutants
|
||||
#
|
||||
# @param [#<<] generator
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new_self(Mutant.random_hex_string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/mutant/mutator/symbol_literal.rb
Normal file
18
lib/mutant/mutator/symbol_literal.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
# Represent mutations on symbol literal
|
||||
class SymbolLiteral < Mutator
|
||||
private
|
||||
# Append mutants
|
||||
#
|
||||
# @param [#<<] generator
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
generator << new_nil
|
||||
generator << new(Rubinius::AST::SymbolLiteral,Mutant.random_hex_string.to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
lib/mutant/mutator/true_literal.rb
Normal file
20
lib/mutant/mutator/true_literal.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
# Represent mutations of true literal
|
||||
class TrueLiteral < Mutator
|
||||
|
||||
private
|
||||
|
||||
# Append mutants
|
||||
#
|
||||
# @param [#<<] generator
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def mutants(generator)
|
||||
generator << new(Rubinius::AST::NilLiteral)
|
||||
generator << new(Rubinius::AST::FalseLiteral)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,14 +12,15 @@ describe Mutant::Mutatee,'#each' do
|
|||
let(:yields) { [] }
|
||||
|
||||
before do
|
||||
Mutant::Mutator.stub(:new => mutator)
|
||||
Mutant::Mutator.stub(:build => mutator)
|
||||
end
|
||||
|
||||
it_should_behave_like 'an #each method'
|
||||
|
||||
#it 'should initialize mutator with ast' do
|
||||
# Mutant::Mutator.should_receive(:new).with(ast).and_return(mutator)
|
||||
#end
|
||||
it 'should initialize mutator with ast' do
|
||||
Mutant::Mutator.should_receive(:build).with(ast).and_return(mutator)
|
||||
subject
|
||||
end
|
||||
|
||||
it 'should yield mutations' do
|
||||
expect { subject }.to change { yields.dup }.from([]).to([mutation])
|
||||
|
|
225
spec/unit/mutant/mutator/each_spec.rb
Normal file
225
spec/unit/mutant/mutator/each_spec.rb
Normal file
|
@ -0,0 +1,225 @@
|
|||
# This file is big. Once mutation interface does not change
|
||||
# anymore it will be split up in mutation specific stuff.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
shared_examples_for 'a mutation enumerator method' do
|
||||
it_should_behave_like 'a command method'
|
||||
|
||||
|
||||
context 'with no block' do
|
||||
subject { object.each }
|
||||
|
||||
it { should be_instance_of(to_enum.class) }
|
||||
|
||||
let(:expected_mutations) do
|
||||
mutations.map do |mutation|
|
||||
if mutation.respond_to?(:to_ast)
|
||||
mutation.to_ast.to_sexp
|
||||
else
|
||||
mutation
|
||||
end
|
||||
end.to_set
|
||||
end
|
||||
|
||||
it 'generates the expected mutations' do
|
||||
subject = self.subject.map(&:to_sexp).to_set
|
||||
|
||||
unless subject == expected_mutations
|
||||
message = "Missing mutations: %s\nUnexpected mutations: %s" %
|
||||
[expected_mutations - subject, subject - expected_mutations ].map(&:to_a).map(&:inspect)
|
||||
fail message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe Mutant::Mutator, '#each' do
|
||||
subject { object.each { |item| yields << item } }
|
||||
|
||||
let(:yields) { [] }
|
||||
let(:object) { class_under_test.new(node) }
|
||||
let(:class_under_test) { described_class.mutator(node) }
|
||||
let(:node) { source.to_ast }
|
||||
let(:random_string) { 'bar' }
|
||||
|
||||
context 'true literal' do
|
||||
let(:source) { 'true' }
|
||||
|
||||
let(:mutations) do
|
||||
%w(nil false)
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
context 'false literal' do
|
||||
let(:source) { 'false' }
|
||||
|
||||
let(:mutations) do
|
||||
%w(nil true)
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
context 'symbol literal' do
|
||||
let(:source) { ':foo' }
|
||||
|
||||
|
||||
let(:mutations) do
|
||||
%w(nil) << ":#{random_string}"
|
||||
end
|
||||
|
||||
before do
|
||||
Mutant.stub(:random_hex_string => random_string)
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
context 'string literal' do
|
||||
let(:source) { '"foo"' }
|
||||
|
||||
let(:mutations) do
|
||||
%W(nil "#{random_string}")
|
||||
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' }
|
||||
|
||||
let(:random_integer) { 5 }
|
||||
let(:mutations) do
|
||||
%W(nil 0 1 #{random_integer}) << [:lit, -10]
|
||||
end
|
||||
|
||||
before do
|
||||
Mutant.stub(:random_fixnum => random_integer)
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
context 'float literal' do
|
||||
let(:source) { '10.0' }
|
||||
|
||||
let(:mutations) do
|
||||
%W(nil 0.0 1.0 #{random_float} 0.0/0.0 1.0/0.0) << [:lit, -10.0]
|
||||
end
|
||||
|
||||
let(:random_float) { 7.123 }
|
||||
|
||||
before do
|
||||
Mutant.stub(:random_float => random_float)
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
context 'empty array literal' do
|
||||
let(:source) { '[]' }
|
||||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
|
||||
# Literal replaced with nil
|
||||
mutations << [:nil]
|
||||
|
||||
# Extra element
|
||||
mutations << '[nil]'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
|
||||
context 'array literal' do
|
||||
let(:source) { '[true, false]' }
|
||||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
|
||||
# Literal replaced with nil
|
||||
mutations << [:nil]
|
||||
|
||||
# Mutation of each element in array
|
||||
mutations << '[nil, false]'
|
||||
mutations << '[false, false]'
|
||||
mutations << '[true, nil]'
|
||||
mutations << '[true, true]'
|
||||
|
||||
# Remove each element of array once
|
||||
mutations << '[true]'
|
||||
mutations << '[false]'
|
||||
|
||||
# Empty array
|
||||
mutations << '[]'
|
||||
|
||||
# Extra element
|
||||
mutations << '[true, false, nil]'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
context 'hash literal' do
|
||||
let(:source) { '{true => true, false => false}' }
|
||||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
|
||||
# Literal replaced with nil
|
||||
mutations << [:nil]
|
||||
|
||||
# Mutation of each key and value in hash
|
||||
mutations << [:hash, [:false ], [:true ], [:false], [:false]]
|
||||
mutations << [:hash, [:nil ], [:true ], [:false], [:false]]
|
||||
mutations << [:hash, [:true ], [:false], [:false], [:false]]
|
||||
mutations << [:hash, [:true ], [:nil ], [:false], [:false]]
|
||||
mutations << [:hash, [:true ], [:true ], [:true ], [:false]]
|
||||
mutations << [:hash, [:true ], [:true ], [:nil ], [:false]]
|
||||
mutations << [:hash, [:true ], [:true ], [:false], [:true ]]
|
||||
mutations << [:hash, [:true ], [:true ], [:false], [:nil ]]
|
||||
|
||||
# Remove each key once
|
||||
mutations << [:hash, [:true ], [:true ]]
|
||||
mutations << [:hash, [:false ], [:false ]]
|
||||
|
||||
# Empty hash
|
||||
mutations << [:hash]
|
||||
|
||||
# Extra element
|
||||
mutations << [:hash, [:true ], [:true ], [:false], [:false ], [:nil], [:nil] ]
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
|
||||
context 'block literal' do
|
||||
let(:source) { "true\nfalse" }
|
||||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
|
||||
# Mutation of each statement in block
|
||||
mutations << "nil\nfalse"
|
||||
mutations << "false\nfalse"
|
||||
mutations << "true\nnil"
|
||||
mutations << "true\ntrue"
|
||||
|
||||
# Remove statement in block
|
||||
mutations << [:block, [:true]]
|
||||
mutations << [:block, [:false]]
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutation enumerator method'
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue