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:
parent
9c1d9cfa58
commit
3e1f9c408f
35 changed files with 635 additions and 641 deletions
1
.rspec
1
.rspec
|
@ -1,2 +1,3 @@
|
|||
--color
|
||||
--backtrace
|
||||
--fail-fast
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 11
|
||||
total_score: 96
|
||||
threshold: 20
|
||||
total_score: 219
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
---
|
||||
threshold: 13.7
|
||||
threshold: 16.0
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
44
lib/mutant/mutator/abstract_range.rb
Normal file
44
lib/mutant/mutator/abstract_range.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
28
lib/mutant/mutator/boolean.rb
Normal file
28
lib/mutant/mutator/boolean.rb
Normal 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
|
19
lib/mutant/mutator/dynamic_string.rb
Normal file
19
lib/mutant/mutator/dynamic_string.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
20
lib/mutant/mutator/regex_literal.rb
Normal file
20
lib/mutant/mutator/regex_literal.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
67
lib/mutant/support/abstract.rb
Normal file
67
lib/mutant/support/abstract.rb
Normal 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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
|
@ -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 }
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue