Merge branch 'master' of github.com:mbj/mutant into flexible-rspec

Conflicts:
	config/flay.yml
This commit is contained in:
Markus Schirp 2013-07-27 18:41:33 +02:00
commit 6bd9d2bf21
49 changed files with 689 additions and 87 deletions

View file

@ -2,7 +2,7 @@ source 'https://rubygems.org'
gemspec gemspec
gem 'mutant', :path => '.' gem 'mutant', path: '.'
gem 'devtools', :git => 'https://github.com/rom-rb/devtools.git' gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
eval(File.read(File.join(File.dirname(__FILE__),'Gemfile.devtools'))) eval(File.read(File.join(File.dirname(__FILE__), 'Gemfile.devtools')))

View file

@ -14,26 +14,27 @@ group :guard do
gem 'guard', '~> 1.8.1' gem 'guard', '~> 1.8.1'
gem 'guard-bundler', '~> 1.0.0' gem 'guard-bundler', '~> 1.0.0'
gem 'guard-rspec', '~> 3.0.2' gem 'guard-rspec', '~> 3.0.2'
# gem 'guard-rubocop', '~> 0.2.0' gem 'guard-rubocop', '~> 0.2.0'
gem 'guard-mutant', '~> 0.0.1'
# file system change event handling # file system change event handling
gem 'listen', '~> 1.2.2' gem 'listen', '~> 1.2.2'
gem 'rb-fchange', '~> 0.0.6', :require => false gem 'rb-fchange', '~> 0.0.6', require: false
gem 'rb-fsevent', '~> 0.9.3', :require => false gem 'rb-fsevent', '~> 0.9.3', require: false
gem 'rb-inotify', '~> 0.9.0', :require => false gem 'rb-inotify', '~> 0.9.0', require: false
# notification handling # notification handling
gem 'libnotify', '~> 0.8.0', :require => false gem 'libnotify', '~> 0.8.0', require: false
gem 'rb-notifu', '~> 0.0.4', :require => false gem 'rb-notifu', '~> 0.0.4', require: false
gem 'terminal-notifier-guard', '~> 1.5.3', :require => false gem 'terminal-notifier-guard', '~> 1.5.3', require: false
end end
group :metrics do group :metrics do
gem 'coveralls', '~> 0.6.7' gem 'coveralls', '~> 0.6.7'
gem 'flay', '~> 2.3.1' gem 'flay', '~> 2.3.1'
gem 'flog', '~> 4.1.1' gem 'flog', '~> 4.1.1'
gem 'reek', '~> 1.3.1', :git => 'https://github.com/troessner/reek.git' gem 'reek', '~> 1.3.1', git: 'https://github.com/troessner/reek.git'
# gem 'rubocop', '~> 0.9.1' gem 'rubocop', '~> 0.10.0', git: 'https://github.com/bbatsov/rubocop.git'
gem 'simplecov', '~> 0.7.1' gem 'simplecov', '~> 0.7.1'
gem 'yardstick', '~> 0.9.6' gem 'yardstick', '~> 0.9.6'
@ -41,10 +42,6 @@ group :metrics do
gem 'yard-spellcheck', '~> 0.1.5' gem 'yard-spellcheck', '~> 0.1.5'
end end
platforms :ruby_19 do
gem 'json', '~> 1.8.0'
end
platforms :rbx do platforms :rbx do
gem 'pelusa', '~> 0.2.2' gem 'pelusa', '~> 0.2.2'
end end

View file

@ -5,7 +5,7 @@ mutant
[![Dependency Status](https://gemnasium.com/mbj/mutant.png)](https://gemnasium.com/mbj/mutant) [![Dependency Status](https://gemnasium.com/mbj/mutant.png)](https://gemnasium.com/mbj/mutant)
[![Code Climate](https://codeclimate.com/github/mbj/mutant.png)](https://codeclimate.com/github/mbj/mutant) [![Code Climate](https://codeclimate.com/github/mbj/mutant.png)](https://codeclimate.com/github/mbj/mutant)
Mutant is a mutation testing tool for ruby. It aims to be better than existing mutation testers. Mutant is a mutation testing tool for ruby.
The idea is that if code can be changed and your tests do not notice, either that code isn't being covered The idea is that if code can be changed and your tests do not notice, either that code isn't being covered
or it does not have a speced side effect. or it does not have a speced side effect.
@ -47,6 +47,36 @@ The 0.2 series is stable but has outdated dependencies. The 0.3 series is in bet
gem install mutant --pre gem install mutant --pre
``` ```
Mutations
---------
Mutant supports a very wide range of mutation operators. Listing them all in detail would blow this document up.
It is planned to parse a list of mutation operators from the source. In the meantime please refer to the
[code](https://github.com/mbj/mutant/tree/master/lib/mutant/mutator/node) each subclass of `Mutant::Mutator::Node`
emits around 3-6 mutations.
Currently mutant covers the majority of ruby's complex nodes that often occur in method bodies.
A some stats from the [axiom](https://github.com/dkubb/axiom) library:
```
Subjects: 417 # Amount of subjects being mutated (currently only methods)
Mutations: 5442 # Amount of mutations mutant generated (~13 mutations per method)
Kills: 5385 # Amount of successfully killed mutations
Runtime: 1898.11s # Total runtime
Killtime: 1884.17s # Time spend killing mutations
Overhead: 0.73%
Coverage: 98.95% # Coverage score
Alive: 57 # Amount of alive mutations.
```
Nodes still missing a dedicated mutator are handled via the
[Generic](https://github.com/mbj/mutant/blob/master/lib/mutant/mutator/node/generic.rb) mutator.
The goal is to remove this mutator and have dedicated mutator for every type of node and removing
the Generic handler altogether.
Examples Examples
-------- --------

View file

@ -1,14 +1,14 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
trap('INT') do |status| trap('INT') do |status|
exit! 128+status exit! 128 + status
end end
require 'mutant' require 'mutant'
namespace = namespace =
if File.basename($0) == 'zombie' if ARGV.include?('--zombie')
$stderr.puts('Detected zombie environment...') $stderr.puts('Running mutant zombified!')
Mutant::Zombifier.zombify Mutant::Zombifier.zombify
Zombie::Mutant Zombie::Mutant
else else

View file

@ -1 +0,0 @@
mutant

View file

@ -1,3 +1,3 @@
--- ---
threshold: 16 threshold: 16
total_score: 685 total_score: 737

View file

@ -47,6 +47,7 @@ NestedIterators:
exclude: exclude:
- Mutant#self.singleton_subclass_instance - Mutant#self.singleton_subclass_instance
- Mutant::Mutator::Util::Array::Element#dispatch - Mutant::Mutator::Util::Array::Element#dispatch
- Mutant::Reporter::CLI::Printer::Config::Runner#generic_stats
- Mutant::CLI#parse - Mutant::CLI#parse
max_allowed_nesting: 1 max_allowed_nesting: 1
ignore_iterators: [] ignore_iterators: []

View file

@ -1,8 +1,11 @@
AllCops: AllCops:
Includes: Includes:
- '../**/*.rake' - '../**/*.rake'
- 'Gemfile'
- 'Gemfile.devtools'
Excludes: Excludes:
- '../vendor/**' - '**/vendor/**'
- '**/benchmarks/**'
# Avoid parameter lists longer than five parameters. # Avoid parameter lists longer than five parameters.
ParameterLists: ParameterLists:

View file

@ -20,7 +20,9 @@ require 'concord'
# Library namespace # Library namespace
module Mutant module Mutant
end # The empty string used within this namespace
EMPTY_STRING = ''.freeze
end # Mutant
require 'mutant/cache' require 'mutant/cache'
require 'mutant/node_helpers' require 'mutant/node_helpers'
@ -52,16 +54,21 @@ require 'mutant/mutator/node/literal/array'
require 'mutant/mutator/node/literal/hash' require 'mutant/mutator/node/literal/hash'
require 'mutant/mutator/node/literal/regex' require 'mutant/mutator/node/literal/regex'
require 'mutant/mutator/node/literal/nil' require 'mutant/mutator/node/literal/nil'
require 'mutant/mutator/node/assignment'
require 'mutant/mutator/node/argument' require 'mutant/mutator/node/argument'
require 'mutant/mutator/node/arguments' require 'mutant/mutator/node/arguments'
require 'mutant/mutator/node/begin' require 'mutant/mutator/node/begin'
require 'mutant/mutator/node/cbase'
require 'mutant/mutator/node/connective/binary'
require 'mutant/mutator/node/const'
require 'mutant/mutator/node/named_value/access'
require 'mutant/mutator/node/named_value/constant_assignment'
require 'mutant/mutator/node/named_value/variable_assignment'
require 'mutant/mutator/node/while' require 'mutant/mutator/node/while'
require 'mutant/mutator/node/super' require 'mutant/mutator/node/super'
require 'mutant/mutator/node/zsuper'
require 'mutant/mutator/node/send' require 'mutant/mutator/node/send'
require 'mutant/mutator/node/send/binary' require 'mutant/mutator/node/send/binary'
require 'mutant/mutator/node/when' require 'mutant/mutator/node/when'
require 'mutant/mutator/node/assignment'
require 'mutant/mutator/node/define' require 'mutant/mutator/node/define'
require 'mutant/mutator/node/mlhs' require 'mutant/mutator/node/mlhs'
require 'mutant/mutator/node/masgn' require 'mutant/mutator/node/masgn'

View file

@ -202,6 +202,8 @@ module Mutant
opts.separator '' opts.separator ''
opts.separator 'Strategies:' opts.separator 'Strategies:'
opts.on('--zombie', 'Run mutant zombified')
add_strategies(opts) add_strategies(opts)
add_options(opts) add_options(opts)
end end

View file

@ -10,7 +10,8 @@ module Mutant
OPERATOR_PATTERN = Regexp.union(*OPERATOR_METHODS.map(&:to_s)).freeze OPERATOR_PATTERN = Regexp.union(*OPERATOR_METHODS.map(&:to_s)).freeze
METHOD_NAME_PATTERN = /([_A-Za-z][A-Za-z0-9_]*[!?=]?|#{OPERATOR_PATTERN})/.freeze METHOD_NAME_PATTERN = /([_A-Za-z][A-Za-z0-9_]*[!?=]?|#{OPERATOR_PATTERN})/.freeze
SCOPE_PATTERN = /(?:::)?#{SCOPE_NAME_PATTERN}(?:::#{SCOPE_NAME_PATTERN})*/.freeze SCOPE_PATTERN = /(?:::)?#{SCOPE_NAME_PATTERN}(?:::#{SCOPE_NAME_PATTERN})*/.freeze
CBASE_PATTERN = /\A::/.freeze
SCOPE_OPERATOR = '::'.freeze
SINGLETON_PATTERN = %r(\A(#{SCOPE_PATTERN})\z).freeze SINGLETON_PATTERN = %r(\A(#{SCOPE_PATTERN})\z).freeze
REGISTRY = [] REGISTRY = []
@ -35,7 +36,7 @@ module Mutant
# @api private # @api private
# #
def self.constant_lookup(location) def self.constant_lookup(location)
location.gsub(%r(\A::), '').split('::').inject(Object) do |parent, name| location.gsub(CBASE_PATTERN, EMPTY_STRING).split(SCOPE_OPERATOR).inject(Object) do |parent, name|
parent.const_get(name) parent.const_get(name)
end end
end end

View file

@ -71,7 +71,7 @@ module Mutant
:kwarg, :restarg, :arg, :block_pass, :or, :and, :kwarg, :restarg, :arg, :block_pass, :or, :and,
:next, :undef, :if, :module, :cbase, :block, :send, :next, :undef, :if, :module, :cbase, :block, :send,
:zsuper, :super, :empty, :alias, :for, :redo, :zsuper, :super, :empty, :alias, :for, :redo,
:return, :splat, :not, :defined?, :op_asgn, :self, :return, :splat, :defined?, :op_asgn, :self,
:true, :false, :nil, :dstr, :dsym, :regexp, :true, :false, :nil, :dstr, :dsym, :regexp,
:regopt, :int, :str, :float, :sym, :pair, :hash, :array, :regopt, :int, :str, :float, :sym, :pair, :hash, :array,
:xstr, :def, :defs, :case, :when, :ivar, :lvar, :cvar, :gvar, :xstr, :def, :defs, :case, :when, :ivar, :lvar, :cvar, :gvar,

View file

@ -18,14 +18,21 @@ module Mutant
def run def run
mutation.insert mutation.insert
# TODO: replace with real streams from configuration # TODO: replace with real streams from configuration
# Note: we assume the only interesting output from a failed rspec run is stderr.
require 'stringio' require 'stringio'
null = StringIO.new rspec_err = StringIO.new
argv = command_line_arguments
begin killed = !::RSpec::Core::Runner.run(command_line_arguments, nil, rspec_err).zero?
!::RSpec::Core::Runner.run(argv, null, null).zero?
rescue StandardError if killed and mutation.should_survive?
true rspec_err.rewind
puts "#{mutation.class} test failed."
puts 'RSpec stderr:'
puts rspec_err.read
end end
killed
end end
# Return command line arguments # Return command line arguments

View file

@ -28,6 +28,18 @@ module Mutant
# #
abstract_method :success? abstract_method :success?
# Indicate if a killer should treat a kill as problematic
#
# @return [true]
# if killing is unexpected
#
# @return [false]
# if killing is expected
#
# @api private
#
abstract_method :should_survive?
# Insert mutated node # Insert mutated node
# #
# FIXME: # FIXME:

View file

@ -30,6 +30,16 @@ module Mutant
killer.killed? killer.killed?
end end
# Indicate if a killer should treat a kill as problematic.
#
# @return [false] Killing evil mutants is not problematic.
#
# @api private
#
def should_survive?
false
end
end # Evil end # Evil
end # Mutation end # Mutation
end # Mutant end # Mutant

View file

@ -10,6 +10,19 @@ module Mutant
SYMBOL = 'noop' SYMBOL = 'noop'
# Indicate if a killer should treat a kill as problematic.
#
# @return [false] Killing noop mutants is a serious problem. Failures
# in noop may indicate a broken test suite, but they can also be an
# indication mutant has altered the runtime environment in a subtle
# way and tickled an odd bug.
#
# @api private
#
def should_survive?
false
end
end end
# Return identification # Return identification
@ -39,6 +52,16 @@ module Mutant
!killer.killed? !killer.killed?
end end
# Indicate if a killer should treat a kill as problematic.
#
# @return [true] Neutral mutants must die.
#
# @api private
#
def should_survive?
false
end
end # Neutral end # Neutral
end # Mutation end # Mutation
end # Mutant end # Mutant

View file

@ -21,7 +21,8 @@ module Mutant
emit_self(*children) emit_self(*children)
end end
end end
children.each do |child| children.each_with_index do |child, index|
mutate_child(index)
emit(child) emit(child)
end end
end end

View file

@ -0,0 +1,25 @@
module Mutant
class Mutator
class Node
# Mutation emitter to handle cbase nodes
class Cbase < self
handle(:cbase)
private
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def dispatch
# noop, for now
end
end # Cbase
end # Node
end # Mutator
end # Mutant

View file

@ -0,0 +1,59 @@
module Mutant
class Mutator
class Node
module Connective
# Mutation emitter to handle binary connectives
class Binary < Node
INVERSE = {
:and => :or,
:or => :and,
}.freeze
handle *INVERSE.keys
children :left, :right
private
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
emit(left)
emit(right)
mutate_operator
mutate_operands
end
# Emit operator mutations
#
# @return [undefined]
#
# @api private
#
def mutate_operator
emit(s(INVERSE.fetch(node.type), left, right))
end
# Emit condition mutations
#
# @return [undefined]
#
# @api private
#
def mutate_operands
emit(s(node.type, n_not(left), right))
emit(n_not(node))
end
end # Binary
end # Connective
end # Node
end # Mutator
end # Mutant

View file

@ -0,0 +1,28 @@
module Mutant
class Mutator
class Node
# Mutation emitter to handle const nodes
class Const < self
handle(:const)
private
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
children.each_with_index do |child, index|
mutate_child(index) if child.kind_of?(Parser::AST::Node)
end
end
end # Const
end # Node
end # Mutator
end # Mutant

View file

@ -1,20 +1,19 @@
module Mutant module Mutant
class Mutator class Mutator
class Node class Node
# Generic mutator # Generic mutator
class Generic < self class Generic < self
handle(:self)
# These nodes still need a dedicated mutator, # These nodes still need a dedicated mutator,
# your contribution is that close! # your contribution is that close!
handle( handle(
:zsuper, :not, :or, :and, :defined, :defined,
:next, :break, :match, :gvar, :cvar, :ensure, :next, :break, :match, :ensure,
:dstr, :dsym, :yield, :rescue, :redo, :defined?, :dstr, :dsym, :yield, :rescue, :redo, :defined?,
:lvar, :const, :blockarg, :block_pass, :op_asgn, :and_asgn, :blockarg, :block_pass, :op_asgn, :and_asgn,
:regopt, :ivar, :restarg, :casgn, :resbody, :retry, :arg_expr, :regopt, :restarg, :resbody, :retry, :arg_expr,
:kwrestarg, :kwoptarg, :kwarg, :undef, :module, :cbase, :empty, :kwrestarg, :kwoptarg, :kwarg, :undef, :module, :empty,
:alias, :for, :xstr, :back_ref, :nth_ref, :class, :alias, :for, :xstr, :back_ref, :nth_ref, :class,
:sclass, :match_with_lvasgn, :match_current_line, :or_asgn, :kwbegin :sclass, :match_with_lvasgn, :match_current_line, :or_asgn, :kwbegin
) )
@ -29,8 +28,7 @@ module Mutant
# #
def dispatch def dispatch
children.each_with_index do |child, index| children.each_with_index do |child, index|
next unless child.kind_of?(Parser::AST::Node) mutate_child(index) if child.kind_of?(Parser::AST::Node)
mutate_child(index)
end end
end end

View file

@ -30,9 +30,9 @@ module Mutant
# #
def mutate_condition def mutate_condition
emit_condition_mutations emit_condition_mutations
emit_self(s(:send, condition, :!), if_branch, else_branch) emit_self(n_not(condition), if_branch, else_branch)
emit_self(s(:true), if_branch, else_branch) emit_self(N_TRUE, if_branch, else_branch)
emit_self(s(:false), if_branch, else_branch) emit_self(N_FALSE, if_branch, else_branch)
end end
# Emit if branch mutations # Emit if branch mutations
@ -45,7 +45,7 @@ module Mutant
emit_self(condition, else_branch, nil) if else_branch emit_self(condition, else_branch, nil) if else_branch
if if_branch if if_branch
emit_if_branch_mutations emit_if_branch_mutations
emit_self(condition, if_branch, nil) emit_self(condition, if_branch, nil)
end end
end end

View file

@ -7,15 +7,21 @@ module Mutant
handle(:regexp) handle(:regexp)
EMPTY_STRING = ''.freeze
# No input can ever be matched with this # No input can ever be matched with this
NULL_REGEXP_SOURCE = 'a\A'.freeze NULL_REGEXP_SOURCE = 'a\A'.freeze
children :source, :options
private private
# Return options
#
# @return [Parser::AST::Node]
#
# @api private
#
def options
children.last
end
# Emit mutants # Emit mutants
# #
# @return [undefined] # @return [undefined]
@ -24,6 +30,9 @@ module Mutant
# #
def dispatch def dispatch
emit_nil emit_nil
children.each_with_index do |child, index|
mutate_child(index) unless child.type == :str
end
emit_self(s(:str, EMPTY_STRING), options) emit_self(s(:str, EMPTY_STRING), options)
emit_self(s(:str, NULL_REGEXP_SOURCE), options) emit_self(s(:str, NULL_REGEXP_SOURCE), options)
end end

View file

@ -2,7 +2,7 @@ module Mutant
class Mutator class Mutator
class Node class Node
# Mutation emitter to handle multipl assignment nodes # Mutation emitter to handle multiple assignment nodes
class MultipleAssignment < self class MultipleAssignment < self
handle(:masgn) handle(:masgn)

View file

@ -0,0 +1,27 @@
module Mutant
class Mutator
class Node
module NamedValue
# Mutation emitter to handle named value access nodes
class Access < Node
handle(:gvar, :cvar, :ivar, :lvar, :self)
private
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
end
end # Access
end # NamedValue
end # Node
end # Mutator
end # Mutant

View file

@ -0,0 +1,42 @@
module Mutant
class Mutator
class Node
module NamedValue
# Mutation emitter to handle constant assignment nodes
class ConstantAssignment < Node
children :cbase, :name, :value
handle :casgn
private
# Perform dispatch
#
# @return [undefined]
#
# @api private
#
def dispatch
mutate_name
emit_value_mutations if value
end
# Emit name mutations
#
# @return [undefined]
#
# @api private
#
def mutate_name
Mutator::Util::Symbol.each(name, self) do |name|
emit_name(name.upcase)
end
end
end # ConstantAssignment
end # NamedValue
end # Node
end # Mutator
end # Mutant

View file

@ -1,11 +1,10 @@
module Mutant module Mutant
class Mutator class Mutator
class Node class Node
# Mutator base class for assignments module NamedValue
class Assignment < self
# Mutator for variable assignment # Mutation emitter to handle variable assignment nodes
class Variable < self class VariableAssignment < Node
children :name, :value children :name, :value
@ -40,12 +39,12 @@ module Mutant
def mutate_name def mutate_name
prefix = MAP.fetch(node.type) prefix = MAP.fetch(node.type)
Mutator::Util::Symbol.each(name, self) do |name| Mutator::Util::Symbol.each(name, self) do |name|
emit_name("#{prefix}#{name}") emit_name(prefix + name.to_s)
end end
end end
end # Variable end # VariableAssignment
end # Assignment end # NamedValue
end # Node end # Node
end # Mutator end # Mutator
end # Mutant end # Mutant

View file

@ -2,7 +2,7 @@ module Mutant
class Mutator class Mutator
class Node class Node
# Mutator for super with parantheses # Mutator for super with parentheses
class Super < self class Super < self
handle(:super) handle(:super)

View file

@ -0,0 +1,25 @@
module Mutant
class Mutator
class Node
# Mutator for super without parentheses
class ZSuper < self
handle(:zsuper)
private
# Emit mutations
#
# @return [undefined]
#
# @api private
#
def dispatch
emit_nil
end
end # ZSuper
end # Node
end # Mutator
end # Mutant

View file

@ -15,7 +15,6 @@ module Mutant
end end
module_function :s module_function :s
NAN = s(:send, s(:float, 0.0), :/, s(:args, s(:float, 0.0))) NAN = s(:send, s(:float, 0.0), :/, s(:args, s(:float, 0.0)))
NEGATIVE_INFINITY = s(:send, s(:float, -1.0), :/, s(:args, s(:float, 0.0))) NEGATIVE_INFINITY = s(:send, s(:float, -1.0), :/, s(:args, s(:float, 0.0)))
INFINITY = s(:send, s(:float, 1.0), :/, s(:args, s(:float, 0.0))) INFINITY = s(:send, s(:float, 1.0), :/, s(:args, s(:float, 0.0)))
@ -23,8 +22,22 @@ module Mutant
RAISE = s(:send, nil, :raise) RAISE = s(:send, nil, :raise)
N_TRUE = s(:true)
N_FALSE = s(:false)
N_NIL = s(:nil) N_NIL = s(:nil)
N_EMPTY = s(:empty) N_EMPTY = s(:empty)
# Build a negated boolean node
#
# @param [Parser::AST::Node] node
#
# @return [Parser::AST::Node]
#
# @api private
#
def n_not(node)
s(:send, node, :!)
end
end # NodeHelpers end # NodeHelpers
end # Mutant end # Mutant

View file

@ -45,6 +45,7 @@ module Mutant
info 'Overhead: %0.2f%%', overhead info 'Overhead: %0.2f%%', overhead
status 'Coverage: %0.2f%%', coverage status 'Coverage: %0.2f%%', coverage
status 'Alive: %s', amount_alive status 'Alive: %s', amount_alive
print_generic_stats
self self
end end
@ -60,6 +61,81 @@ module Mutant
object.subjects object.subjects
end end
# Walker for all ast nodes
class Walker
# Run walkter
#
# @param [Parser::AST::Node] root
#
# @return [self]
#
# @api private
#
def self.run(root, &block)
new(root, block)
self
end
private_class_method :new
# Initialize and run walker
#
# @param [Parser::AST::Node] root
# @param [#call(node)] block
#
# @return [undefined]
#
# @api private
#
def initialize(root, block)
@root, @block = root, block
dispatch(root)
end
private
# Perform dispatch
#
# @return [undefined]
#
# @api private
#
def dispatch(node)
@block.call(node)
node.children.grep(Parser::AST::Node).each(&method(:dispatch))
end
end
# Print generic stats
#
# @return [undefined]
#
# @api private
#
def print_generic_stats
stats = generic_stats.to_a.sort_by(&:last)
info('Nodes handled by generic mutator (type:occurrences):')
stats.reverse_each do |type, amount|
info('%-10s: %d', type, amount)
end
end
# Return stats for nodes handled by generic mutator
#
# @return [Hash<Symbo, Fixnum>]
#
# @api private
#
def generic_stats
object.subjects.each_with_object(Hash.new(0)) do |runner, stats|
Walker.run(runner.subject.node) do |node|
next unless Mutator::Registry.lookup(node) == Mutator::Node::Generic
stats[node.type] += 1
end
end
end
# Return amount of subjects # Return amount of subjects
# #
# @return [Fixnum] # @return [Fixnum]

View file

@ -2,7 +2,7 @@
Gem::Specification.new do |gem| Gem::Specification.new do |gem|
gem.name = 'mutant' gem.name = 'mutant'
gem.version = '0.3.0.beta17' gem.version = '0.3.0.beta21'
gem.authors = [ 'Markus Schirp' ] gem.authors = [ 'Markus Schirp' ]
gem.email = [ 'mbj@schirp-dso.com' ] gem.email = [ 'mbj@schirp-dso.com' ]

View file

@ -24,7 +24,7 @@ RSpec.configure do |config|
config.include(CompressHelper) config.include(CompressHelper)
config.include(ParserHelper) config.include(ParserHelper)
config.include(Mutant::NodeHelpers) config.include(Mutant::NodeHelpers)
config.mock_with :rspec do |config| config.mock_with :rspec do |rspec|
config.syntax = [:expect, :should] rspec.syntax = [:expect, :should]
end end
end end

View file

@ -6,7 +6,7 @@ describe Mutant::Killer::Rspec, '.new' do
let(:strategy) { double('Strategy', :spec_files => ['foo'], :error_stream => $stderr, :output_stream => $stdout) } let(:strategy) { double('Strategy', :spec_files => ['foo'], :error_stream => $stderr, :output_stream => $stdout) }
let(:context) { double('Context') } let(:context) { double('Context') }
let(:mutation) { double('Mutation', :subject => mutation_subject) } let(:mutation) { double('Mutation', :subject => mutation_subject, :should_survive? => false) }
let(:mutation_subject) { double('Mutation Subject') } let(:mutation_subject) { double('Mutation Subject') }
let(:object) { described_class } let(:object) { described_class }

View file

@ -0,0 +1,17 @@
require 'spec_helper'
describe Mutant::Mutator::Node::NamedValue::Access, 'cbase' do
before do
Mutant::Random.stub(:hex_string => :random)
end
let(:source) { '::A' }
let(:mutations) do
mutants = []
mutants << 'nil'
end
it_should_behave_like 'a mutator'
end

View file

@ -0,0 +1,39 @@
require 'spec_helper'
describe Mutant::Mutator::Node::Connective::Binary, 'mutations' do
context 'and' do
let(:source) { 'true and false' }
let(:mutations) do
mutations = []
mutations << 'nil'
mutations << 'true'
mutations << 'false'
mutations << 'true or false'
mutations << 'not true and false'
mutations << 'not(true and false)'
end
it_should_behave_like 'a mutator'
end
context 'or' do
let(:source) { 'true or false' }
let(:mutations) do
mutations = []
mutations << 'nil'
mutations << 'true'
mutations << 'false'
mutations << 'true and false'
mutations << 'not true or false'
mutations << 'not(true or false)'
end
it_should_behave_like 'a mutator'
end
end

View file

@ -0,0 +1,18 @@
require 'spec_helper'
describe Mutant::Mutator::Node::NamedValue::Access, 'const' do
before do
Mutant::Random.stub(:hex_string => :random)
end
let(:source) { 'A::B' }
let(:mutations) do
mutants = []
mutants << 'nil'
mutants << 'nil::B'
end
it_should_behave_like 'a mutator'
end

View file

@ -2,14 +2,31 @@ require 'spec_helper'
describe Mutant::Mutator::Node::Literal, 'regex' do describe Mutant::Mutator::Node::Literal, 'regex' do
let(:source) { '/foo/' } context 'literal' do
let(:source) { '/foo/' }
let(:mutations) do let(:mutations) do
mutations = [] mutations = []
mutations << 'nil' mutations << 'nil'
mutations << '//' # match all mutations << '//' # match all
mutations << '/a\A/' # match nothing mutations << '/a\A/' # match nothing
end
it_should_behave_like 'a mutator'
end
context 'interpolated' do
let(:source) { '/#{foo.bar}n/' }
let(:mutations) do
mutations = []
mutations << 'nil'
mutations << '//' # match all
mutations << '/#{foo}n/' # match all
mutations << '/a\A/' # match nothing
end
it_should_behave_like 'a mutator'
end end
it_should_behave_like 'a mutator'
end end

View file

@ -6,7 +6,6 @@ describe Mutant::Mutator, 'masgn' do
Mutant::Random.stub(:hex_string => 'random') Mutant::Random.stub(:hex_string => 'random')
end end
let(:source) { 'a, b = c, d' } let(:source) { 'a, b = c, d' }
let(:mutations) do let(:mutations) do

View file

@ -0,0 +1,77 @@
require 'spec_helper'
describe Mutant::Mutator::Node::NamedValue::Access, 'mutations' do
before do
Mutant::Random.stub(:hex_string => :random)
end
context 'global variable' do
let(:source) { '$a = nil; $a' }
let(:mutations) do
mutants = []
mutants << '$a = nil; nil'
mutants << '$a = nil'
mutants << '$a'
mutants << '$a = ::Object.new; $a'
mutants << '$srandom = nil; $a'
end
it_should_behave_like 'a mutator'
end
context 'class variable' do
let(:source) { '@@a = nil; @@a' }
let(:mutations) do
mutants = []
mutants << '@@a = nil; nil'
mutants << '@@a = nil'
mutants << '@@a'
mutants << '@@a = ::Object.new; @@a'
mutants << '@@srandom = nil; @@a'
end
end
context 'instance variable' do
let(:source) { '@a = nil; @a' }
let(:mutations) do
mutants = []
mutants << '@a = nil; nil'
mutants << '@a = nil'
mutants << '@a'
mutants << '@a = ::Object.new; @a'
mutants << '@srandom = nil; @a'
end
it_should_behave_like 'a mutator'
end
context 'local variable' do
let(:source) { 'a = nil; a' }
let(:mutations) do
mutants = []
mutants << 'a = nil; nil'
mutants << 'a = nil'
mutants << 'a'
mutants << 'a = ::Object.new; a'
mutants << 'srandom = nil; a'
end
it_should_behave_like 'a mutator'
end
context 'self' do
let(:source) { 'self' }
let(:mutations) do
mutants = []
mutants << 'nil'
end
it_should_behave_like 'a mutator'
end
end

View file

@ -0,0 +1,20 @@
require 'spec_helper'
describe Mutant::Mutator::Node::NamedValue::VariableAssignment, 'mutations' do
before do
Mutant::Random.stub(:hex_string => :random)
end
let(:source) { 'A = true' }
let(:mutations) do
mutations = []
mutations << 'SRANDOM = true'
mutations << 'A = false'
mutations << 'A = nil'
end
it_should_behave_like 'a mutator'
end

View file

@ -1,6 +1,6 @@
require 'spec_helper' require 'spec_helper'
describe Mutant::Mutator::Node::Assignment, 'mutations' do describe Mutant::Mutator::Node::NamedValue::VariableAssignment, 'mutations' do
before do before do
Mutant::Random.stub(:hex_string => :random) Mutant::Random.stub(:hex_string => :random)

View file

@ -9,6 +9,7 @@ describe Mutant::Mutator, 'send' do
let(:mutations) do let(:mutations) do
mutations = [] mutations = []
mutations << 'foo ||= expression' mutations << 'foo ||= expression'
mutations << 'nil.foo ||= expression'
end end
it_should_behave_like 'a mutator' it_should_behave_like 'a mutator'
@ -74,6 +75,7 @@ describe Mutant::Mutator, 'send' do
mutations = [] mutations = []
mutations << 'foo' mutations << 'foo'
mutations << 'self' mutations << 'self'
mutations << 'nil.foo'
end end
it_should_behave_like 'a mutator' it_should_behave_like 'a mutator'
@ -111,6 +113,7 @@ describe Mutant::Mutator, 'send' do
mutations = [] mutations = []
mutations << 'self.class' mutations << 'self.class'
mutations << 'self.foo' mutations << 'self.foo'
mutations << 'nil.class.foo'
end end
it_should_behave_like 'a mutator' it_should_behave_like 'a mutator'
@ -142,6 +145,7 @@ describe Mutant::Mutator, 'send' do
mutations << 'foo(nil)' mutations << 'foo(nil)'
mutations << 'nil' mutations << 'nil'
mutations << 'self.foo(::Object.new)' mutations << 'self.foo(::Object.new)'
mutations << 'nil.foo(nil)'
end end
it_should_behave_like 'a mutator' it_should_behave_like 'a mutator'
@ -187,6 +191,8 @@ describe Mutant::Mutator, 'send' do
mutations = [] mutations = []
mutations << 'foo' mutations << 'foo'
mutations << 'left - right' mutations << 'left - right'
mutations << 'left / foo'
mutations << 'right / foo'
end end
it_should_behave_like 'a mutator' it_should_behave_like 'a mutator'

View file

@ -5,7 +5,12 @@ describe Mutant::Mutator, 'super' do
context 'with no arguments' do context 'with no arguments' do
let(:source) { 'super' } let(:source) { 'super' }
it_should_behave_like 'a noop mutator' let(:mutations) do
mutations = []
mutations << 'nil'
end
it_should_behave_like 'a mutator'
end end
context 'with explicit empty arguments' do context 'with explicit empty arguments' do

View file

@ -1,7 +0,0 @@
require 'spec_helper'
describe Mutant::Mutator, 'self' do
let(:source) { 'self' }
it_should_behave_like 'a noop mutator'
end

View file

@ -0,0 +1,12 @@
require 'spec_helper'
describe Mutant::NodeHelpers, '#n_not' do
subject { object.n_not(node) }
let(:object) { Object.new.extend(described_class) }
let(:node) { described_class::N_TRUE }
it 'returns the negated node' do
expect(subject).to eq(parse('not true'))
end
end

View file

@ -30,7 +30,10 @@ shared_examples_for 'a mutator' do
unless subject == expected_mutations unless subject == expected_mutations
message = "Missing mutations: %s\nUnexpected mutations: %s" % message = "Missing mutations: %s\nUnexpected mutations: %s" %
[expected_mutations - subject, subject - expected_mutations ].map(&:to_a).map(&:inspect) [
expected_mutations - subject,
subject - expected_mutations
].map(&:to_a).map(&:inspect)
fail message fail message
end end
end end

View file

@ -6,4 +6,6 @@ require 'test_app'
require 'rspec' require 'rspec'
# require spec support files and shared behavior # require spec support files and shared behavior
Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require f } Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each do |file|
require file
end

View file

@ -1,6 +1,6 @@
require 'spec_helper' require 'spec_helper'
describe TestApp::Literal,'#string' do describe TestApp::Literal, '#string' do
subject { object.string } subject { object.string }
let(:object) { described_class.new } let(:object) { described_class.new }