Introduce noop mutation guards and argument mutators
* Sorry for not splitting up into smaller commit
This commit is contained in:
parent
5fed2cb57f
commit
40d5230c68
24 changed files with 478 additions and 133 deletions
|
@ -1,6 +1,10 @@
|
|||
# v0.2.4 2012-12-08
|
||||
|
||||
* [feature] define block arguments
|
||||
* [feature] Run noop mutation per subject to guard against initial failing specs
|
||||
* [feature] Mutate default into required arguments
|
||||
* [feature] Mutate default literals
|
||||
* [feature] Mutate unwinding of pattern args |(a, b), c] => |a, b, c|
|
||||
* [feature] Mutate define and block arguments
|
||||
* [feature] Mutate block arguments, inklusive pattern args
|
||||
* [feature] Recurse into block bodies
|
||||
* [fixed] Crash on mutating yield, added a noop for now
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -2,5 +2,7 @@ source 'https://rubygems.org'
|
|||
|
||||
gemspec
|
||||
|
||||
gem 'to_source', :path => '../to_source'
|
||||
|
||||
gem 'devtools', :git => 'https://github.com/mbj/devtools.git'
|
||||
eval(File.read(File.join(File.dirname(__FILE__),'Gemfile.devtools')))
|
||||
|
|
|
@ -9,11 +9,21 @@ require 'digest/sha1'
|
|||
require 'to_source'
|
||||
require 'inflector'
|
||||
require 'ice_nine'
|
||||
require 'ice_nine/core_ext/object'
|
||||
require 'diff/lcs'
|
||||
require 'diff/lcs/hunk'
|
||||
require 'rspec'
|
||||
|
||||
module IceNine
|
||||
class Freezer
|
||||
class Rubinius
|
||||
class AST < IceNine::Freezer::Object
|
||||
class Node < IceNine::Freezer::Object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Library namespace
|
||||
module Mutant
|
||||
|
||||
|
@ -74,6 +84,7 @@ require 'mutant/mutator/node/send'
|
|||
require 'mutant/mutator/node/arguments'
|
||||
require 'mutant/mutator/node/define'
|
||||
require 'mutant/mutator/node/return'
|
||||
require 'mutant/mutator/node/local_variable_assignment'
|
||||
require 'mutant/mutator/node/iter_19'
|
||||
require 'mutant/mutator/node/if_statement'
|
||||
require 'mutant/mutator/node/receiver_case'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Mutant
|
||||
# Comandline parser
|
||||
class CLI
|
||||
include Adamantium::Flat, Equalizer.new(:matcher, :filter, :killer)
|
||||
include Adamantium::Flat, Equalizer.new(:matcher, :filter, :strategy, :reporter)
|
||||
|
||||
# Error raised when CLI argv is inalid
|
||||
Error = Class.new(RuntimeError)
|
||||
|
@ -44,6 +44,20 @@ module Mutant
|
|||
end
|
||||
memoize :matcher
|
||||
|
||||
# Test for running in debug mode
|
||||
#
|
||||
# @return [true]
|
||||
# if debug mode is active
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def debug?
|
||||
!!@debug
|
||||
end
|
||||
|
||||
# Return mutation filter
|
||||
#
|
||||
# @return [Mutant::Matcher]
|
||||
|
@ -67,7 +81,9 @@ module Mutant
|
|||
#
|
||||
def strategy
|
||||
@strategy || raise(Error, 'no strategy was set!')
|
||||
@strategy.new(self)
|
||||
end
|
||||
memoize :strategy
|
||||
|
||||
# Return reporter
|
||||
#
|
||||
|
@ -76,7 +92,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def reporter
|
||||
Mutant::Reporter::CLI.new($stdout)
|
||||
Mutant::Reporter::CLI.new(self)
|
||||
end
|
||||
memoize :reporter
|
||||
|
||||
|
@ -88,7 +104,8 @@ module Mutant
|
|||
'--include' => [:add_load_path ],
|
||||
'-r' => [:require_library ],
|
||||
'--require' => [:require_library ],
|
||||
#'--killer-fork' => [:enable_killer_fork ],
|
||||
'--debug' => [:set_debug ],
|
||||
'-d' => [:set_debug ],
|
||||
'--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ],
|
||||
'--rspec-full' => [:set_strategy, Strategy::Rspec::Full ],
|
||||
'--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ],
|
||||
|
@ -98,33 +115,6 @@ module Mutant
|
|||
|
||||
OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze
|
||||
|
||||
# Return selected killer
|
||||
#
|
||||
# @return [Killer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def selected_killer
|
||||
unless @rspec
|
||||
raise Error, "Only rspec is supported currently use --rspec switch"
|
||||
end
|
||||
|
||||
Mutant::Killer::Rspec
|
||||
end
|
||||
memoize :selected_killer
|
||||
|
||||
# Return option for argument with index
|
||||
#
|
||||
# @param [Fixnum] index
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def option(index)
|
||||
@arguments.fetch(index+1)
|
||||
end
|
||||
|
||||
# Initialize CLI
|
||||
#
|
||||
# @param [Array<String>] arguments
|
||||
|
@ -146,6 +136,18 @@ module Mutant
|
|||
matcher
|
||||
end
|
||||
|
||||
# Return option for argument with index
|
||||
#
|
||||
# @param [Fixnum] index
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def option(index)
|
||||
@arguments.fetch(index+1)
|
||||
end
|
||||
|
||||
# Return current argument
|
||||
#
|
||||
# @return [String]
|
||||
|
@ -268,17 +270,15 @@ module Mutant
|
|||
@rspec = true
|
||||
end
|
||||
|
||||
# Enable killer forking
|
||||
# Set debug mode
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# @return [self]
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def enable_killer_fork
|
||||
def set_debug
|
||||
consume(1)
|
||||
@forking = true
|
||||
@debug = true
|
||||
end
|
||||
|
||||
# Set strategy
|
||||
|
@ -304,6 +304,5 @@ module Mutant
|
|||
require(current_option_value)
|
||||
consume(2)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,7 +29,7 @@ module Mutant
|
|||
#
|
||||
def run
|
||||
mutation.insert
|
||||
!::RSpec::Core::Runner.run(command_line_arguments, @error_stream, @output_stream).zero?
|
||||
!::RSpec::Core::Runner.run(command_line_arguments, strategy.error_stream, strategy.output_stream).zero?
|
||||
end
|
||||
memoize :run
|
||||
|
||||
|
|
|
@ -109,5 +109,31 @@ module Mutant
|
|||
def initialize(subject, node)
|
||||
@subject, @node = subject, node
|
||||
end
|
||||
|
||||
class Noop < self
|
||||
|
||||
# Initialihe object
|
||||
#
|
||||
# @param [Subject] subject
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(subject)
|
||||
super(subject, subject.node)
|
||||
end
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
"noop:#{super}"
|
||||
end
|
||||
memoize :identification
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,16 @@ module Mutant
|
|||
end
|
||||
private_class_method :handle
|
||||
|
||||
# Return identity of object (for deduplication)
|
||||
#
|
||||
# @param [Object]
|
||||
#
|
||||
# @return [Object]
|
||||
#
|
||||
def self.identity(object)
|
||||
object
|
||||
end
|
||||
|
||||
# Return input
|
||||
#
|
||||
# @return [Object]
|
||||
|
@ -52,12 +62,13 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def initialize(input, block)
|
||||
@input, @block = Helper.deep_clone(input), block
|
||||
IceNine.deep_freeze(@input)
|
||||
@input, @block = IceNine.deep_freeze(input), block
|
||||
@seen = Set.new
|
||||
guard(input)
|
||||
dispatch
|
||||
end
|
||||
|
||||
# Test if generated object is different from input
|
||||
# Test if generated object is not guarded from emmitting
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
|
@ -69,7 +80,19 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def new?(object)
|
||||
input != object
|
||||
!@seen.include?(self.class.identity(object))
|
||||
end
|
||||
|
||||
# Add object to guarded values
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def guard(object)
|
||||
@seen << self.class.identity(object)
|
||||
end
|
||||
|
||||
# Test if generated mutation is allowed
|
||||
|
@ -105,6 +128,8 @@ module Mutant
|
|||
def emit(object)
|
||||
return unless new?(object) and allow?(object)
|
||||
|
||||
guard(object)
|
||||
|
||||
emit!(object)
|
||||
end
|
||||
|
||||
|
@ -160,7 +185,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def dup_input
|
||||
input.dup
|
||||
Helper.deep_clone(input)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,36 +5,21 @@ module Mutant
|
|||
class Node < self
|
||||
include AbstractType
|
||||
|
||||
# Return identity of node
|
||||
#
|
||||
# @param [Rubinius::AST::Node] node
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
def self.identity(node)
|
||||
ToSource.to_source(node)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
alias_method :node, :input
|
||||
alias_method :dup_node, :dup_input
|
||||
|
||||
# Return source of input node
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def source
|
||||
ToSource.to_source(node)
|
||||
end
|
||||
memoize :source
|
||||
|
||||
# Test if generated node is new
|
||||
#
|
||||
# @return [true]
|
||||
# if generated node is different from input
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def new?(node)
|
||||
source != ToSource.to_source(node)
|
||||
end
|
||||
|
||||
# Emit a new AST node
|
||||
#
|
||||
# @param [Rubinis::AST::Node:Class] node_class
|
||||
|
@ -116,8 +101,8 @@ module Mutant
|
|||
|
||||
Mutator.each(body) do |mutation|
|
||||
dup = dup_node
|
||||
yield mutation if block_given?
|
||||
dup.public_send(:"#{name}=", mutation)
|
||||
yield dup if block_given?
|
||||
emit(dup)
|
||||
end
|
||||
end
|
||||
|
@ -156,9 +141,7 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def dup_node
|
||||
node.dup
|
||||
end
|
||||
alias_method :dup_node, :dup_input
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,23 +17,24 @@ module Mutant
|
|||
def dispatch
|
||||
emit_attribute_mutations(:name)
|
||||
end
|
||||
end
|
||||
|
||||
# Test if node is new
|
||||
# Mutantor for default arguments
|
||||
class DefaultArguments < self
|
||||
handle(Rubinius::AST::DefaultArguments)
|
||||
|
||||
private
|
||||
|
||||
# Emit mutations
|
||||
#
|
||||
# Note: to_source does not handle PatternVariableNodes as entry points
|
||||
#
|
||||
# @param [Rubinius::AST::Node] generated
|
||||
#
|
||||
# @return [true]
|
||||
# if node is new
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def new?(generated)
|
||||
node.name != generated.name
|
||||
def dispatch
|
||||
emit_attribute_mutations(:arguments) do |argument|
|
||||
argument.names = argument.arguments.map(&:name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -53,9 +54,7 @@ module Mutant
|
|||
def dispatch
|
||||
Mutator.each(node.arguments.body) do |mutation|
|
||||
dup = dup_node
|
||||
dup_args = dup.arguments.dup
|
||||
dup_args.body = mutation
|
||||
dup.arguments = dup_args
|
||||
dup.arguments.body = mutation
|
||||
emit(dup)
|
||||
end
|
||||
end
|
||||
|
@ -90,7 +89,51 @@ module Mutant
|
|||
#
|
||||
def dispatch
|
||||
expand_pattern_args
|
||||
emit_attribute_mutations(:required)
|
||||
emit_default_mutations
|
||||
emit_required_defaults_mutation
|
||||
emit_attribute_mutations(:required) do |mutation|
|
||||
mutation.names = mutation.optional + mutation.required
|
||||
end
|
||||
end
|
||||
|
||||
# Emit default mutations
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def emit_default_mutations
|
||||
return unless node.defaults
|
||||
emit_attribute_mutations(:defaults) do |mutation|
|
||||
mutation.optional = mutation.defaults.names
|
||||
mutation.names = mutation.required + mutation.optional
|
||||
if mutation.defaults.names.empty?
|
||||
mutation.defaults = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Emit required defaults mutations
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def emit_required_defaults_mutation
|
||||
return unless node.defaults
|
||||
arguments = node.defaults.arguments
|
||||
arguments.each_index do |index|
|
||||
names = arguments.take(index+1).map(&:name)
|
||||
dup = dup_node
|
||||
defaults = dup.defaults
|
||||
defaults.arguments = defaults.arguments.drop(names.size)
|
||||
names.each { |name| dup.optional.delete(name) }
|
||||
dup.required.concat(names)
|
||||
if dup.optional.empty?
|
||||
dup.defaults = nil
|
||||
end
|
||||
emit(dup)
|
||||
end
|
||||
end
|
||||
|
||||
# Emit pattern args expansions
|
||||
|
@ -102,13 +145,12 @@ module Mutant
|
|||
def expand_pattern_args
|
||||
node.required.each_with_index do |argument, index|
|
||||
next unless argument.kind_of?(Rubinius::AST::PatternArguments)
|
||||
required = node.required.dup
|
||||
dup = dup_node
|
||||
required = dup.required
|
||||
required.delete_at(index)
|
||||
argument.arguments.body.reverse.each do |node|
|
||||
required.insert(index, node.name)
|
||||
end
|
||||
dup = dup_node
|
||||
dup.required = required
|
||||
dup.names |= required
|
||||
emit(dup)
|
||||
end
|
||||
|
|
|
@ -15,7 +15,8 @@ module Mutant
|
|||
def dispatch
|
||||
emit_attribute_mutations(:body)
|
||||
emit_attribute_mutations(:arguments) do |mutation|
|
||||
mutation.names = mutation.required
|
||||
arguments = mutation.arguments
|
||||
arguments.names = arguments.required + arguments.optional
|
||||
end if node.arguments
|
||||
end
|
||||
|
||||
|
|
25
lib/mutant/mutator/node/local_variable_assignment.rb
Normal file
25
lib/mutant/mutator/node/local_variable_assignment.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class Node
|
||||
class LocalVariableAssignment < self
|
||||
|
||||
handle(Rubinius::AST::LocalVariableAssignment)
|
||||
|
||||
private
|
||||
|
||||
# Emit mutants
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit_attribute_mutations(:name)
|
||||
emit_attribute_mutations(:value)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -34,6 +34,7 @@ module Mutant
|
|||
handle(Rubinius::AST::File)
|
||||
handle(Rubinius::AST::DynamicRegex)
|
||||
handle(Rubinius::AST::OpAssignOr19)
|
||||
handle(Rubinius::AST::BlockPass19)
|
||||
handle(Rubinius::AST::OpAssign1)
|
||||
handle(Rubinius::AST::Or)
|
||||
handle(Rubinius::AST::ConstantAccess)
|
||||
|
|
|
@ -52,5 +52,33 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
abstract_method :config
|
||||
|
||||
# Return output stream
|
||||
#
|
||||
# @return [IO]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :output_stream
|
||||
|
||||
# Return error stream
|
||||
#
|
||||
# @return [IO]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :error_stream
|
||||
|
||||
private
|
||||
|
||||
# Initialize reporter
|
||||
#
|
||||
# @param [Config] config
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,26 @@ module Mutant
|
|||
puts("Subject: #{subject.identification}")
|
||||
end
|
||||
|
||||
# Return error stream
|
||||
#
|
||||
# @return [IO]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def error_stream
|
||||
@config.debug? ? io : StringIO.new
|
||||
end
|
||||
|
||||
# Return output stream
|
||||
#
|
||||
# @return [IO]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def output_stream
|
||||
@config.debug? ? io : StringIO.new
|
||||
end
|
||||
|
||||
# Report mutation
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
|
@ -26,7 +46,10 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def mutation(mutation)
|
||||
#colorized_diff(mutation.original_source, mutation.source)
|
||||
if @config.debug?
|
||||
colorized_diff(mutation)
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -45,6 +68,32 @@ module Mutant
|
|||
puts "Strategy: #{config.strategy.inspect}"
|
||||
end
|
||||
|
||||
# Report noop
|
||||
#
|
||||
# @param [Killer] killer
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def noop(killer)
|
||||
color, word =
|
||||
if killer.fail?
|
||||
[Color::GREEN, 'Alive']
|
||||
else
|
||||
[Color::RED, 'Killed']
|
||||
end
|
||||
|
||||
puts(colorize(color, "#{word}: #{killer.identification} (%02.2fs)" % killer.runtime))
|
||||
|
||||
unless killer.fail?
|
||||
puts(killer.mutation.source)
|
||||
stats.noop_fail(killer)
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Reporter killer
|
||||
#
|
||||
# @param [Killer] killer
|
||||
|
@ -66,8 +115,7 @@ module Mutant
|
|||
puts(colorize(color, "#{word}: #{killer.identification} (%02.2fs)" % killer.runtime))
|
||||
|
||||
if killer.fail?
|
||||
mutation = killer.mutation
|
||||
colorized_diff(mutation.original_source, mutation.source)
|
||||
colorized_diff(killer.mutation)
|
||||
end
|
||||
|
||||
self
|
||||
|
@ -87,12 +135,13 @@ module Mutant
|
|||
end
|
||||
|
||||
puts
|
||||
puts "subjects: #{stats.subject}"
|
||||
puts "mutations: #{stats.mutation}"
|
||||
puts "kills: #{stats.kill}"
|
||||
puts "alive: #{stats.alive}"
|
||||
puts "mtime: %02.2fs" % stats.time
|
||||
puts "rtime: %02.2fs" % stats.runtime
|
||||
puts "subjects: #{stats.subjects}"
|
||||
puts "mutations: #{stats.mutations}"
|
||||
puts "noop_fails: #{stats.noop_fails}"
|
||||
puts "kills: #{stats.kills}"
|
||||
puts "alive: #{stats.alive}"
|
||||
puts "mtime: %02.2fs" % stats.time
|
||||
puts "rtime: %02.2fs" % stats.runtime
|
||||
end
|
||||
|
||||
# Return IO stream
|
||||
|
@ -121,8 +170,9 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(io)
|
||||
@io = io
|
||||
def initialize(config)
|
||||
super
|
||||
@io = $stdout
|
||||
@stats = Stats.new
|
||||
end
|
||||
|
||||
|
@ -136,7 +186,7 @@ module Mutant
|
|||
#
|
||||
def failure(killer)
|
||||
puts(colorize(Color::RED, "!!! Mutant alive: #{killer.identification} !!!"))
|
||||
colorized_diff(killer.original_source, killer.mutation_source)
|
||||
colorized_diff(killer.mutation)
|
||||
puts("Took: (%02.2fs)" % killer.runtime)
|
||||
end
|
||||
|
||||
|
@ -187,17 +237,25 @@ module Mutant
|
|||
# @param [String] original
|
||||
# @param [String] current
|
||||
#
|
||||
# @return [self]
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def colorized_diff(original, current)
|
||||
def colorized_diff(mutation)
|
||||
if mutation.kind_of?(Mutation::Noop)
|
||||
io.mutation.original_source
|
||||
return
|
||||
end
|
||||
|
||||
original, current = mutation.original_source, mutation.source
|
||||
differ = Differ.new(original, current)
|
||||
diff = color? ? differ.colorized_diff : differ.diff
|
||||
|
||||
# FIXME remove this branch before release
|
||||
if diff.empty?
|
||||
raise "Unable to create a diff, so ast mutation or to_source has an error!"
|
||||
end
|
||||
|
||||
puts(diff)
|
||||
self
|
||||
end
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
module Mutant
|
||||
class Reporter
|
||||
# Null reporter
|
||||
Null = Class.new(self) do
|
||||
|
||||
class Null < self
|
||||
|
||||
# Report subject
|
||||
#
|
||||
# @param [Subject] subject
|
||||
|
@ -37,6 +38,7 @@ module Mutant
|
|||
def killer(*)
|
||||
self
|
||||
end
|
||||
end.new.freeze
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :subject
|
||||
attr_reader :subjects
|
||||
|
||||
# Return mutation count
|
||||
#
|
||||
|
@ -18,7 +18,15 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :mutation
|
||||
attr_reader :mutations
|
||||
|
||||
# Return skip count
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :noop_fails
|
||||
|
||||
# Return kill count
|
||||
#
|
||||
|
@ -26,7 +34,7 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :kill
|
||||
attr_reader :kills
|
||||
|
||||
# Return mutation runtime
|
||||
#
|
||||
|
@ -38,7 +46,7 @@ module Mutant
|
|||
|
||||
def initialize
|
||||
@start = Time.now
|
||||
@subject = @mutation = @kill = @time = 0
|
||||
@noop_fails = @subjects = @mutations = @kills = @time = 0
|
||||
end
|
||||
|
||||
def runtime
|
||||
|
@ -46,19 +54,27 @@ module Mutant
|
|||
end
|
||||
|
||||
def subject
|
||||
@subject +=1
|
||||
@subjects +=1
|
||||
self
|
||||
end
|
||||
|
||||
def alive
|
||||
@mutation - @kill
|
||||
@mutations - @kills
|
||||
end
|
||||
|
||||
def noop_fail(killer)
|
||||
@noop_fails += 1
|
||||
@time += killer.runtime
|
||||
self
|
||||
end
|
||||
|
||||
def killer(killer)
|
||||
@mutation +=1
|
||||
@kill +=1 unless killer.fail?
|
||||
@mutations +=1
|
||||
@kills +=1 unless killer.fail?
|
||||
@time += killer.runtime
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,6 +86,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def run_subject(subject)
|
||||
return unless noop(subject)
|
||||
subject.each do |mutation|
|
||||
next unless config.filter.match?(mutation)
|
||||
reporter.mutation(mutation)
|
||||
|
@ -93,20 +94,58 @@ module Mutant
|
|||
end
|
||||
end
|
||||
|
||||
# Test for noop mutation
|
||||
#
|
||||
# @param [Subject] subject
|
||||
#
|
||||
# @return [true]
|
||||
# if noop mutation is okay
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def noop(subject)
|
||||
killer = killer(subject.noop)
|
||||
reporter.noop(killer)
|
||||
unless killer.fail?
|
||||
@errors << killer
|
||||
false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Run killer on mutation
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [undefined]
|
||||
# @return [true]
|
||||
# if killer was unsuccessful
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def kill(mutation)
|
||||
killer = config.strategy.kill(mutation)
|
||||
killer = killer(mutation)
|
||||
reporter.killer(killer)
|
||||
|
||||
if killer.fail?
|
||||
@errors << killer
|
||||
end
|
||||
end
|
||||
|
||||
# Return killer for mutation
|
||||
#
|
||||
# @return [Killer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def killer(mutation)
|
||||
config.strategy.kill(mutation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,46 @@
|
|||
module Mutant
|
||||
class Strategy
|
||||
include AbstractType
|
||||
include AbstractType, Adamantium::Flat, Equalizer.new
|
||||
|
||||
# Return config
|
||||
#
|
||||
# @return [Config]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :config
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
# @param [Config] config
|
||||
#
|
||||
# @return [undefined
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
# Return output stream
|
||||
#
|
||||
# @return [IO]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def output_stream
|
||||
config.reporter.output_stream
|
||||
end
|
||||
|
||||
# Return error stream
|
||||
#
|
||||
# @return [IO]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def error_stream
|
||||
config.reporter.error_stream
|
||||
end
|
||||
|
||||
# Kill mutation
|
||||
#
|
||||
|
@ -10,7 +50,7 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.kill(mutation)
|
||||
def kill(mutation)
|
||||
killer.new(self, mutation)
|
||||
end
|
||||
|
||||
|
@ -20,12 +60,13 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.killer
|
||||
self::KILLER
|
||||
def killer
|
||||
self.class::KILLER
|
||||
end
|
||||
|
||||
# Static strategies
|
||||
class Static < self
|
||||
include Equalizer.new
|
||||
|
||||
# Always fail to kill strategy
|
||||
class Fail < self
|
||||
|
|
|
@ -15,7 +15,7 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.spec_files(mutation)
|
||||
def spec_files(mutation)
|
||||
ExampleLookup.run(mutation)
|
||||
end
|
||||
end
|
||||
|
@ -29,7 +29,7 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.spec_files(mutation)
|
||||
def spec_files(mutation)
|
||||
['spec/unit']
|
||||
end
|
||||
end
|
||||
|
@ -43,14 +43,14 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.spec_files(mutation)
|
||||
def spec_files(mutation)
|
||||
Dir['spec/integration/**/*_spec.rb']
|
||||
end
|
||||
end
|
||||
|
||||
# Run all specs per mutation
|
||||
class Full < self
|
||||
def self.spec_files(mutation)
|
||||
def spec_files(mutation)
|
||||
Dir['spec/**/*_spec.rb']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,17 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Return noop mutation
|
||||
#
|
||||
# @return [Mutation::Noop]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def noop
|
||||
Mutation::Noop.new(self)
|
||||
end
|
||||
memoize :noop
|
||||
|
||||
# Return subject identicication
|
||||
#
|
||||
# @return [String]
|
||||
|
|
|
@ -15,13 +15,25 @@ shared_examples_for 'a mutator' do
|
|||
|
||||
it { should be_instance_of(to_enum.class) }
|
||||
|
||||
def assert_transitive(ast)
|
||||
generated = ToSource.to_source(ast)
|
||||
parsed = generated.to_ast
|
||||
again = ToSource.to_source(parsed)
|
||||
unless generated == again
|
||||
fail "Untransitive:\n%s\n---\n%s" % [generated, again]
|
||||
end
|
||||
end
|
||||
|
||||
unless instance_methods.include?(:expected_mutations)
|
||||
let(:expected_mutations) do
|
||||
mutations.map do |mutation|
|
||||
case mutation
|
||||
when String
|
||||
mutation.to_ast
|
||||
ast = mutation.to_ast
|
||||
assert_transitive(ast)
|
||||
ast
|
||||
when Rubinius::AST::Node
|
||||
assert_transitive(mutation)
|
||||
mutation
|
||||
else
|
||||
raise
|
||||
|
|
|
@ -4,7 +4,7 @@ describe Mutant::Killer::Rspec, '.new' do
|
|||
|
||||
subject { object.new(strategy, mutation) }
|
||||
|
||||
let(:strategy) { mock('Strategy', :spec_files => ['foo']) }
|
||||
let(:strategy) { mock('Strategy', :spec_files => ['foo'], :error_stream => $stderr, :output_stream => $stdout) }
|
||||
let(:context) { mock('Context') }
|
||||
let(:mutation) { mock('Mutation') }
|
||||
|
||||
|
|
|
@ -48,6 +48,25 @@ describe Mutant::Mutator, 'define' do
|
|||
mutations << 'def foo(a, b); Object.new; end'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
end
|
||||
|
||||
context 'default argument' do
|
||||
let(:source) { 'def foo(a = "literal"); end' }
|
||||
|
||||
before do
|
||||
Mutant::Random.stub(:hex_string => 'random')
|
||||
end
|
||||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
mutations << 'def foo(a); end'
|
||||
mutations << 'def foo(); end'
|
||||
mutations << 'def foo(a = "random"); end'
|
||||
mutations << 'def foo(a = nil); end'
|
||||
mutations << 'def foo(a = "literal"); Object.new; end'
|
||||
mutations << 'def foo(srandom = "literal"); nil; end'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Mutator, 'call' do
|
||||
describe Mutant::Mutator, 'send' do
|
||||
context 'send without arguments' do
|
||||
# This could not be reproduced in a test case but happens in the mutant source code?
|
||||
context 'block_given?' do
|
||||
|
|
Loading…
Add table
Reference in a new issue