Cleanup and dedup mutation generation

* Mutator and Generator where merged.
* A single pass over all duplications was made.
* It is clear a specific handles?(node) code for finding mutators
  is needed. Like virtus does for attributes, should also cache.
* Does not pass on 1.9 mode currently as blocks are unexpectly parsed
  differend when it comes to a series of literal booleans.
This commit is contained in:
Markus Schirp 2012-07-31 04:00:05 +02:00
parent 9c1d9cfa58
commit 3e1f9c408f
35 changed files with 635 additions and 641 deletions

1
.rspec
View file

@ -1,2 +1,3 @@
--color
--backtrace
--fail-fast

View file

@ -4,7 +4,7 @@ guard :bundler do
watch('Gemfile')
end
guard :rspec, :version => 2, :all_on_start => false do
guard :rspec, :version => 2 do
# run all specs if the spec_helper or supporting files files are modified
watch('spec/spec_helper.rb') { 'spec' }
watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' }

View file

@ -1,3 +1,3 @@
---
threshold: 11
total_score: 96
threshold: 20
total_score: 219

View file

@ -1,2 +1,2 @@
---
threshold: 13.7
threshold: 16.0

View file

@ -1,10 +1,10 @@
---
AbcMetricMethodCheck:
score: 12.1
score: 12.2
AssignmentInConditionalCheck: {}
CaseMissingElseCheck: {}
ClassLineCountCheck:
line_count: 200
line_count: 324
ClassNameCheck:
pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/
ClassVariableCheck: {}
@ -19,7 +19,7 @@ MethodLineCountCheck:
MethodNameCheck:
pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/
ModuleLineCountCheck:
line_count: 202
line_count: 327
ModuleNameCheck:
pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/
ParameterNumberCheck:

View file

@ -5,51 +5,6 @@ require 'securerandom'
# Library namespace
module Mutant
# Helper method for raising not implemented exceptions
#
# @param [Object] object
# the object where method is not implemented
#
# @raise [NotImplementedError]
# raises a not implemented error with correct description
#
# @example
# class Foo
# def bar
# Mutant.not_implemented(self)
# end
# end
#
# Foo.new.x # raises NotImplementedError "Foo#bar is not implemented"
#
# @return [undefined]
#
# @api private
#
def self.not_implemented(object)
method = caller(1).first[/`(.*)'/,1].to_sym
constant_name,delimiter = not_implemented_info(object)
raise NotImplementedError,"#{constant_name}#{delimiter}#{method} is not implemented"
end
# Return name and delimiter
#
# @param [Object] object
#
# @return [Array]
#
# @api private
#
def self.not_implemented_info(object)
if object.kind_of?(Module)
[object.name,'.']
else
[object.class.name,'#']
end
end
private_class_method :not_implemented_info
# Return random string
#
# @return [String]
@ -81,10 +36,17 @@ module Mutant
end
end
require 'mutant/support/abstract'
require 'mutant/mutator'
require 'mutant/mutator/generator'
require 'mutant/mutator/abstract_range'
require 'mutant/mutator/range'
require 'mutant/mutator/range_exclude'
require 'mutant/mutator/boolean'
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'
@ -92,8 +54,6 @@ require 'mutant/mutator/float_literal'
require 'mutant/mutator/array_literal'
require 'mutant/mutator/empty_array'
require 'mutant/mutator/hash_literal'
require 'mutant/mutator/range'
require 'mutant/mutator/range_exclude'
require 'mutant/mutator/regex_literal'
require 'mutant/mutator/dynamic_string'
require 'mutant/mutator/block'

View file

@ -2,6 +2,7 @@ module Mutant
# An abstract context where mutations can be appied to.
class Context
include Veritas::Immutable
extend Abstract
# Return root ast for mutated node
#
@ -11,8 +12,6 @@ module Mutant
#
# @api private
#
def root(node)
Mutant.not_implemented(self)
end
abstract :root
end
end

View file

@ -2,6 +2,7 @@ module Mutant
# Abstract matcher to find ASTs to mutate
class Matcher
include Enumerable
extend Abstract
# Enumerate mutatees
#
@ -9,9 +10,7 @@ module Mutant
#
# @return [undefined]
#
def each
Mutant.not_implemented(self)
end
abstract :each
private

View file

@ -86,9 +86,7 @@ module Mutant
#
# @api private
#
def method
Mutant.not_implemented(self)
end
abstract :method
# Return node classes this matcher matches
#
@ -96,9 +94,7 @@ module Mutant
#
# @api private
#
def node_class
Mutant.not_implemented(self)
end
abstract :node_class
# Check if node is matched
#
@ -164,9 +160,7 @@ module Mutant
#
# @api private
#
def matched_node
Mutant.not_implemented(self)
end
abstract :matched_node
# Return mutatee
#

View file

@ -31,7 +31,7 @@ module Mutant
#
def each(&block)
return to_enum unless block_given?
Mutator.build(node).each(&block)
Mutator.each(node,&block)
self
end

View file

@ -1,18 +1,23 @@
module Mutant
# Mutate rubinius AST nodes
# Generator for mutations
class Mutator
include Enumerable, Veritas::Immutable
include Veritas::Immutable
extend Abstract
# Build mutation node
# Enumerate mutations on node
#
# @param [Rubinius::AST::Node] node
# @param [#call] block
#
# @return [Mutator]
# @return [self]
#
# @api private
#
def self.build(node)
mutator(node).new(node)
def self.each(node,&block)
return to_enum(__method__,node) unless block_given?
generator(node).new(node,block)
self
end
# Return mutator for node or raise
@ -26,50 +31,131 @@ module Mutant
#
# @api private
#
def self.mutator(node)
def self.generator(node)
unqualified_name = node.class.name.split('::').last
const_get(unqualified_name)
const_get(unqualified_name)
end
# Enumerate mutated asts
#
# @api private
#
# @return [self]
# returns self if block given
#
# @return [Enumerator<Rubnius::AST::Node>]
# returns enumerator on AST nodes otherwise
#
# @api private
#
def each(&block)
return to_enum unless block_given?
mutants(Generator.new(@node,block))
self
end
private_class_method :generator
private
# Initialize generator
#
# @param [Rubinius::AST::Node] node
# @param [#call(node)] block
#
# @return [undefined]
#
# @api private
#
def initialize(node,block)
@node,@block = node,block
dispatch
end
# Return wrapped node
#
# @return [Rubinius::AST::Node]
# @return [Rubius::AST::Node]
#
# @api private
#
attr_reader :node
private :node
# Initialize mutator with
# Dispatch node generations
#
# @param [Rubinius::AST::Node] node
# @return [undefined]
#
# @api private
#
def initialize(node)
@node = node
abstract :dispatch
# Append node to generated mutations if node does not equal orignal node
#
# @param [Rubinius::AST::Node] node
#
# @return [self]
#
# @api private
#
def emit_safe(node)
return self unless new?(node)
emit_unsafe(node)
end
# Create a new AST node
# 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 emit_new
MAX_TRIES.times do
node = yield
if new?(node)
emit_unsafe(node)
return
end
end
raise "New AST could not be generated after #{MAX_TRIES} attempts"
end
# Call block with node
#
# @param [Rubinius::AST::Node] node
#
# @return [self]
#
# @api private
#
def emit_unsafe(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
# Return s-expressions for node
#
# @return [Array]
#
# @api private
#
def sexp
node.to_sexp
end
# Emit a new AST node
#
# @param [Rubinis::AST::Node:Class] node_class
#
@ -77,8 +163,8 @@ module Mutant
#
# @api private
#
def new(node_class,*arguments)
node_class.new(node.line,*arguments)
def emit(node_class,*arguments)
emit_safe(new(node_class,*arguments))
end
# Create a new AST node with same class as wrapped node
@ -91,9 +177,9 @@ module Mutant
new(node.class,*arguments)
end
# Create a new AST node with NilLiteral class
# Create a new AST node with Rubnius::AST::NilLiteral class
#
# @return [Rubinius::AST::NilLiteral]
# @return [Rubinius::AST::Node]
#
# @api private
#
@ -101,16 +187,143 @@ module Mutant
new(Rubinius::AST::NilLiteral)
end
# Append mutations
# Create a new AST node with the same line as wrapped node
#
# @param [Class:Rubinius::AST::Node] node_class
#
# @return [Rubinius::AST::Node]
#
# @api private
#
# @param [#<<] generator
def new(node_class,*arguments)
node_class.new(node.line,*arguments)
end
# Emit a new AST node with same class as wrapped node
#
# @return [undefined]
#
def mutants(generator)
Mutant.not_implemented(self)
# @api private
#
def emit_self(*arguments)
emit_safe(new_self(*arguments))
end
# Emit a new node with wrapping class for each entry in values
#
# @param [Array] values
#
# @return [undefined]
#
# @api private
#
def emit_values(values)
values.each do |value|
emit_self(value)
end
end
# Emit element presence mutations
#
# @param [Array] elements
#
# @return [undefined]
#
# @api private
#
def emit_element_presence(elements)
elements.each_index do |index|
dup_elements = elements.dup
dup_elements.delete_at(index)
emit_self(dup_elements)
end
end
# Emit element mutations
#
# @param [Array] elements
#
# @return [undefined]
#
# @api private
#
def emit_elements(elements)
elements.each_with_index do |element,index|
dup_elements = elements.dup
Mutator.each(element).each do |mutation|
dup_elements[index]=mutation
emit_self(dup_elements)
end
end
end
# Emit a new AST node with NilLiteral class
#
# @return [Rubinius::AST::NilLiteral]
#
# @api private
#
def emit_nil
emit_safe(new_nil)
end
# Return AST representing negative infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def negative_infinity
new(Rubinius::AST::Negate,infinity)
end
# Return AST representing infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def infinity
new_call(new_float(1),:/,new_float(0))
end
# Return AST representing send
#
# @param [Rubinius::AST::Node] receiver
# @param [Symbol] name
# @param [Rubinius::AST::Node] arguments
#
# @return [Rubnius::AST::SendWithArguments]
#
# @api private
#
def new_call(receiver,name,arguments)
new(Rubinius::AST::SendWithArguments,receiver,name,arguments)
end
# Return new float literal
#
# @param [#to_f] value
#
# @return [Rubinius::Node::FloatLiteral]
#
# @api private
#
def new_float(value)
new(Rubinius::AST::FloatLiteral,value)
end
# Return AST representing NaN
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def nan
new_call(new_float(0),:/,new_float(0))
end
memoize :sexp
end
end

View file

@ -0,0 +1,44 @@
module Mutant
class Mutator
# Abstract literal range mutator
class AbstractRange < Mutator
private
# Emit mutants
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
emit_safe(inverse(node.start,node.finish))
emit_range
end
# Emit range specific mutants
#
# @return [undefined]
#
# @api private
#
def emit_range
start,finish = node.start,node.finish
emit_self(negative_infinity,finish)
emit_self(nan,finish)
emit_self(start,infinity)
emit_self(start,nan)
end
# Return inverse AST node class
#
# @return [Class:Rubinius::AST::Node]
#
# @api private
#
abstract :inverse_class
end
end
end

View file

@ -5,74 +5,19 @@ module Mutant
private
# Append mutants to generator
#
# @param [#<<] generator
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def mutants(generator)
generator << new_nil
generator << new_self([])
generator << new_self(dup_body << new_nil)
mutate_elements(generator)
mutate_presence(generator)
end
# Return array literal body
#
# @return [Array]
#
# @api private
#
def body
node.body
end
# Return duplicated body on each call
#
# @return [Array]
#
# @api private
#
def dup_body
body.dup
end
# Append mutations on element presence
#
# @param [#<<] generator
#
# @api private
#
# @return [undefined]
#
def mutate_presence(generator)
body.each_index do |index|
body = dup_body
body.delete_at(index)
generator << new_self(body)
end
end
# Append mutations on elements
#
# @param [#<<] generator
#
# @api private
#
# @return [undefined]
#
def mutate_elements(generator)
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
def dispatch
body = node.body
emit_nil
emit_self([])
emit_self(body.dup << new_nil)
emit_element_presence(body)
emit_elements(body)
end
end
end

View file

@ -5,71 +5,16 @@ module Mutant
private
# Append mutations to block
#
# @param [#<<] generator
# Emit mutants
#
# @return [undefined]
#
# @api private
#
def mutants(generator)
mutate_elements(generator)
mutate_presence(generator)
end
# Return block array
#
# @return [Array]
#
# @api private
#
def array
node.array
end
# Return duplicated block array each call
#
# @return [Array]
#
# @api private
#
def dup_array
array.dup
end
# Append mutations on block member presence
#
# @param [#<<] generator
#
# @return [undefined]
#
# @api private
#
def mutate_presence(generator)
array.each_index do |index|
array = dup_array
array.delete_at(index)
generator << new_self(array)
end
end
# Append mutations on block elements
#
# @param [#<<] generator
#
# @return [undefined]
#
# @api private
#
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
def dispatch
array = node.array
emit_elements(array)
emit_element_presence(array)
end
end
end

View file

@ -0,0 +1,28 @@
module Mutant
class Mutator
# Abstract mutator for boolean literals
class Boolean < Mutator
private
# Emit mutants
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
emit_safe(inverse)
end
# Return inverse
#
# @return [Rubinius::AST::Node]
#
# @api private
#
abstract :inverse
end
end
end

View file

@ -0,0 +1,19 @@
module Mutant
class Mutator
# Represent mutations on dynamic literal
class DynamicString < Mutator
private
# Emit mutants
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
end
end
end
end

View file

@ -5,17 +5,15 @@ module Mutant
private
# Append mutations on empty literals
#
# @param [#<<] generator
# Emit mutants
#
# @return [undefined]
#
# @api private
#
def mutants(generator)
generator << new_nil
generator << new(Rubinius::AST::ArrayLiteral,[new_nil])
def dispatch
emit_nil
emit(Rubinius::AST::ArrayLiteral,[new_nil])
end
end
end

View file

@ -1,19 +1,18 @@
module Mutant
class Mutator
# Represent mutations of false literal
class FalseLiteral < Mutator
class FalseLiteral < Boolean
private
# Append mutants
# Return inverse class
#
# @param [#<<] generator
# @return [Rubinius::AST::TrueLiteral]
#
# @return [undefined]
# @api private
#
def mutants(generator)
generator << new_nil
generator << new(Rubinius::AST::TrueLiteral)
def inverse
new(Rubinius::AST::TrueLiteral)
end
end
end

View file

@ -2,21 +2,29 @@ module Mutant
class Mutator
# Represent mutations on fixnum literal
class FixnumLiteral < Mutator
private
# Append mutants
#
# @param [#<<] generator
# Emit mutants
#
# @return [undefined]
#
def mutants(generator)
generator << new(Rubinius::AST::NilLiteral)
generator << new_self(0)
generator << new_self(1)
generator << new_self(-node.value)
generator.generate do
new_self(Mutant.random_fixnum)
end
# @api private
#
def dispatch
emit_nil
emit_values(values)
emit_new { new_self(Mutant.random_fixnum) }
end
# Return values to mutate against
#
# @return [Array]
#
# @api private
#
def values
[0,1,-node.value]
end
end
end

View file

@ -2,60 +2,30 @@ module Mutant
class Mutator
# Represent mutations on fixnum literal
class FloatLiteral < Mutator
private
# Append mutants
#
# @param [#<<] generator
# Emit mutants
#
# @return [undefined]
#
def mutants(generator)
generator << new_nil
generator << new_self(0.0)
generator << new_self(1.0)
generator << new_self(-node.value)
generator.generate do
new_self(Mutant.random_float)
end
generator << infinity
generator << neg_infinity
generator << nan
def dispatch
emit_nil
emit_values(values)
emit_safe(infinity)
emit_safe(negative_infinity)
emit_safe(nan)
emit_new { new_self(Mutant.random_float) }
end
# Return AST representing NaN
# Return values to test against
#
# @return [Rubinius::Node::AST]
# @return [Array]
#
# @api private
#
def nan
'0.0/0.0'.to_ast.tap do |call|
call.line = node.line
end
end
# Return AST representing infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def infinity
'1.0/0.0'.to_ast.tap do |call|
call.line = node.line
end
end
# Return AST representing negative Infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def neg_infinity
'-1.0/0.0'.to_ast.tap do |call|
call.line = node.line
end
def values
[0.0,1.0] << -node.value
end
end
end

View file

@ -1,95 +0,0 @@
module Mutant
class Mutator
class Generator
# 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 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
# 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
end

View file

@ -5,23 +5,20 @@ module Mutant
private
# Append mutants for hash literals
#
# @param [#<<] generator
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def mutants(generator)
generator << new_nil
generator << new_self([])
generator << new_self(array + [new_nil, new_nil])
mutate_elements(generator)
mutate_presence(generator)
def dispatch
emit_nil
emit_values(values)
emit_element_presence
emit_elements(array)
end
# Return hash literal node array
# Return array of values in literal
#
# @return [Array]
#
@ -31,7 +28,7 @@ module Mutant
node.array
end
# Return duplicated literal array on each call
# Return duplicate of array values in literal
#
# @return [Array]
#
@ -41,38 +38,28 @@ module Mutant
array.dup
end
# Append mutations on pair presence
#
# @param [#<<] generator
#
# @return [undefined]
# Return values to mutate against
#
# @return [Array]
#
# @api private
#
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
def values
[[], dup_array << new_nil << new_nil ]
end
# Append mutations on members
#
# @param [#<<] generator
# Emit element presence mutations
#
# @return [undefined]
#
# @api private
#
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
def emit_element_presence
0.step(array.length-1,2) do |index|
contents = dup_array
contents.delete_at(index)
contents.delete_at(index)
emit_self(contents)
end
end
end

View file

@ -1,60 +1,18 @@
module Mutant
class Mutator
# Mutator for range literal AST nodes
class Range < Mutator
class Range < AbstractRange
private
# Append mutations on range literals
# Return inverse
#
# @param [#<<] generator
#
# @return [undefined]
# @return [Rubinius::AST::RangeExclude]
#
# @api private
#
def mutants(generator)
generator << new_nil
generator << new(Rubinius::AST::RangeExclude,node.start,node.finish)
generator << new_self(neg_infinity,node.finish)
generator << new_self(nan,node.finish)
generator << new_self(node.start,infinity)
generator << new_self(node.start,nan)
end
# Return AST representing infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def neg_infinity
'-1.0/0.0'.to_ast.tap do |call|
call.line = node.line
end
end
# Return AST representing infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def infinity
'1.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
'0.0/0.0'.to_ast.tap do |call|
call.line = node.line
end
def inverse(*arguments)
new(Rubinius::AST::RangeExclude,*arguments)
end
end
end

View file

@ -1,61 +1,18 @@
module Mutant
class Mutator
# Mutator for range exclude literals
class RangeExclude < Mutator
class RangeExclude < AbstractRange
private
# Append mutations on range exclude literals
# Return inverse node
#
# @param [#<<] generator
#
# @return [undefined]
# @return [Rubnius::AST::Range]
#
# @api private
#
def mutants(generator)
generator << new_nil
generator << new(Rubinius::AST::Range,node.start,node.finish)
generator << new_self(neg_infinity,node.finish)
generator << new_self(nan,node.finish)
generator << new_self(node.start,infinity)
generator << new_self(node.start,nan)
end
# Return AST representing infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def neg_infinity
'-1.0/0.0'.to_ast.tap do |call|
call.line = node.line
end
end
# Return AST representing infinity
#
# @return [Rubinius::Node::AST]
#
# @api private
#
def infinity
'1.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
'0.0/0.0'.to_ast.tap do |call|
call.line = node.line
end
def inverse(*arguments)
new(Rubinius::AST::Range,*arguments)
end
end
end

View file

@ -0,0 +1,20 @@
module Mutant
class Mutator
# Mutator for regexp literal AST nodes
class RegexLiteral < Mutator
private
# Emit mutants
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
emit_new { new_self(Mutant.random_hex_string,node.options) }
end
end
end
end

View file

@ -5,17 +5,15 @@ module Mutant
private
# Append mutants
#
# @param [#<<] generator
# Emit mutants
#
# @return [undefined]
#
def mutants(generator)
generator << new_nil
generator.generate do
new_self(Mutant.random_hex_string)
end
# @api private
#
def dispatch
emit_nil
emit_new { new_self(Mutant.random_hex_string) }
end
end
end

View file

@ -2,18 +2,18 @@ module Mutant
class Mutator
# Represent mutations on symbol literal
class SymbolLiteral < Mutator
private
# Append mutants
#
# @param [#<<] generator
# Emit mutatns
#
# @return [undefined]
#
def mutants(generator)
generator << new_nil
generator.generate do
new_self(Mutant.random_hex_string.to_sym)
end
# @api private
#
def dispatch
emit_nil
emit_new { new_self(Mutant.random_hex_string.to_sym) }
end
end
end

View file

@ -1,19 +1,18 @@
module Mutant
class Mutator
# Represent mutations of true literal
class TrueLiteral < Mutator
class TrueLiteral < Boolean
private
# Append mutants
# Return inverse
#
# @param [#<<] generator
# @return [Rubinius::AST::FalseLiteral]
#
# @return [undefined]
# @api private
#
def mutants(generator)
generator << new_nil
generator << new(Rubinius::AST::FalseLiteral)
def inverse
new(Rubinius::AST::FalseLiteral)
end
end
end

View file

@ -0,0 +1,67 @@
module Mutant
# Module to declare abstract methods
module Abstract
# Declare abstract instance method
#
# @return [self]
#
# @api private
#
def abstract(*names)
names.each do |name|
define_instance_method(name)
end
self
end
# Declare abstract singleton method
#
# @return [self]
#
# @api private
#
def abstract_singleton(*names)
names.each do |name|
define_singleton_method(name)
end
self
end
private
# Define abstract method stub on singleton
#
# @param [String] name
#
# @return [undefined]
#
# @api private
#
def define_singleton_method(name)
class_eval(<<-RUBY,__FILE__,__LINE__+1)
def #{name}(*)
raise NotImplementedError,"\#{self.name}.\#{__method__} is not implemented"
end
RUBY
end
# Define abstract method stub on instances
#
# @param [String] name
#
# @return [undefined]
#
# @api private
#
def define_instance_method(name)
class_eval(<<-RUBY,__FILE__,__LINE__+1)
def #{name}(*)
raise NotImplementedError,"\#{self.class.name}#\#{__method__} is not implemented"
end
RUBY
end
end
end

View file

@ -1,35 +0,0 @@
require 'spec_helper'
describe Mutant,'.not_implemented' do
let(:object) { described_class.new }
let(:described_class) do
Class.new do
def foo
Mutant.not_implemented(self)
end
def self.foo
Mutant.not_implemented(self)
end
def self.name
'Test'
end
end
end
context 'on instance method' do
subject { object.foo }
it 'should raise error' do
expect { subject }.to raise_error(NotImplementedError,'Test#foo is not implemented')
end
end
context 'on singleton method' do
subject { described_class.foo }
it 'should raise error' do
expect { subject }.to raise_error(NotImplementedError,'Test.foo is not implemented')
end
end
end

View file

@ -8,17 +8,16 @@ describe Mutant::Mutatee,'#each' do
let(:ast) { mock('AST') }
let(:context) { mock('Context', :root => root) }
let(:mutation) { mock('Mutation') }
let(:mutator) { [mutation] }
let(:yields) { [] }
before do
Mutant::Mutator.stub(:build => mutator)
Mutant::Mutator.stub(:each).with(ast).and_yield(mutation).and_return(Mutant::Mutator)
end
it_should_behave_like 'an #each method'
#it_should_behave_like 'an #each method'
it 'should initialize mutator with ast' do
Mutant::Mutator.should_receive(:build).with(ast).and_return(mutator)
Mutant::Mutator.should_receive(:each).with(ast).and_yield(mutation).and_return(Mutant::Mutator)
subject
end

View file

@ -8,7 +8,7 @@ shared_examples_for 'a mutation enumerator method' do
context 'with no block' do
subject { object.each }
subject { object.each(node) }
it { should be_instance_of(to_enum.class) }
@ -35,14 +35,13 @@ shared_examples_for 'a mutation enumerator method' do
end
describe Mutant::Mutator, '#each' do
subject { object.each { |item| yields << item } }
describe Mutant::Mutator, '.each' do
subject { object.each(node) { |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' }
let(:yields) { [] }
let(:object) { described_class }
let(:node) { source.to_ast }
let(:random_string) { 'bar' }
context 'true literal' do
let(:source) { 'true' }
@ -127,7 +126,15 @@ describe Mutant::Mutator, '#each' do
let(:source) { '10.0' }
let(:mutations) do
%W(nil 0.0 1.0 #{random_float} 0.0/0.0 1.0/0.0 -1.0/0.0) << [:lit, -10.0]
mutations = []
mutations << 'nil'
mutations << '0.0'
mutations << '1.0'
mutations << random_float.to_s
mutations << '0.0/0.0'
mutations << '1.0/0.0'
mutations << [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]]
mutations << [:lit, -10.0]
end
let(:random_float) { 7.123 }
@ -226,7 +233,7 @@ describe Mutant::Mutator, '#each' do
mutations << 'nil'
mutations << '1...100'
mutations << '(0.0/0.0)..100'
mutations << '(-1.0/0.0)..100'
mutations << [:dot2, [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]], [:lit, 100]]
mutations << '1..(1.0/0.0)'
mutations << '1..(0.0/0.0)'
end
@ -242,7 +249,7 @@ describe Mutant::Mutator, '#each' do
mutations << 'nil'
mutations << '1..100'
mutations << '(0.0/0.0)...100'
mutations << '(-1.0/0.0)...100'
mutations << [:dot3, [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]], [:lit, 100]]
mutations << '1...(1.0/0.0)'
mutations << '1...(0.0/0.0)'
end

View file

@ -1,7 +1,7 @@
require 'spec_helper'
describe Mutant::Mutator::Generator,'#generate' do
subject { object.generate { node } }
describe Mutant::Mutator,'#emit_new' do
subject { object.send(:emit_new) { node } }
class Block
attr_reader :arguments
@ -15,9 +15,17 @@ describe Mutant::Mutator::Generator,'#generate' do
end
end
let(:object) { described_class.new(wrapped_node,block) }
let(:block) { Block.new }
let(:wrapped_node) { '"foo"'.to_ast }
let(:object) { class_under_test.new(wrapped_node,block) }
let(:block) { Block.new }
let(:wrapped_node) { '"foo"'.to_ast }
let(:class_under_test) do
Class.new(described_class) do
def dispatch
#noop
end
end
end
context 'when new AST is generated' do
let(:node) { '"bar"'.to_ast }

View file

@ -1,7 +1,7 @@
require 'spec_helper'
describe Mutant::Mutator::Generator,'#append' do
subject { object.append(node) }
describe Mutant::Mutator,'#emit_safe' do
subject { object.send(:emit_safe,node) }
class Block
attr_reader :arguments
@ -15,10 +15,18 @@ describe Mutant::Mutator::Generator,'#append' do
end
end
let(:object) { described_class.new(wrapped_node,block) }
let(:object) { class_under_test.new(wrapped_node,block) }
let(:block) { Block.new }
let(:wrapped_node) { '"foo"'.to_ast }
let(:class_under_test) do
Class.new(described_class) do
def dispatch
#noop
end
end
end
context 'with node that is not equal to wrapped node' do
let(:node) { '"bar"'.to_ast }

View file

@ -1,43 +1,37 @@
begin
if RUBY_VERSION == '1.8.7' and !defined?(RUBY_ENGINE)
require 'flay'
require 'yaml'
require 'flay'
require 'yaml'
config = YAML.load_file(File.expand_path('../../../config/flay.yml', __FILE__)).freeze
threshold = config.fetch('threshold').to_i
total_score = config.fetch('total_score').to_f
files = Flay.expand_dirs_to_files(config.fetch('path', 'lib'))
config = YAML.load_file(File.expand_path('../../../config/flay.yml', __FILE__)).freeze
threshold = config.fetch('threshold').to_i
total_score = config.fetch('total_score').to_f
files = Flay.expand_dirs_to_files(config.fetch('path', 'lib'))
# original code by Marty Andrews:
# http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
desc 'Analyze for code duplication'
task :flay do
# run flay once without a threshold to ensure the max mass matches the threshold
flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0)
flay.process(*files)
# original code by Marty Andrews:
# http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
desc 'Analyze for code duplication'
task :flay do
# run flay once without a threshold to ensure the max mass matches the threshold
flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0)
flay.process(*files)
max = flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max
unless max >= threshold
raise "Adjust flay threshold down to #{max}"
end
total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) }
unless total == total_score
raise "Flay total is now #{total}, but expected #{total_score}"
end
# run flay a second time with the threshold set
flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ)
flay.process(*files)
if flay.masses.any?
flay.report
raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}"
end
max = flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max
unless max >= threshold
raise "Adjust flay threshold down to #{max}"
end
else
task :flay do
$stderr.puts 'Flay has inconsistend results accros ruby implementations. It is only enabled on 1.8.7, fix and remove guard'
total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) }
unless total == total_score
raise "Flay total is now #{total}, but expected #{total_score}"
end
# run flay a second time with the threshold set
flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ)
flay.process(*files)
if flay.masses.any?
flay.report
raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}"
end
end
rescue LoadError