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:
Markus Schirp 2012-07-27 22:39:31 +02:00
parent a8e635d77a
commit 9e8b451933
16 changed files with 666 additions and 15 deletions

6
TODO
View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -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])

View 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