Merge branch 'master' of github.com:mbj/mutant into flexible-rspec
Conflicts: config/flay.yml
This commit is contained in:
commit
6bd9d2bf21
49 changed files with 689 additions and 87 deletions
6
Gemfile
6
Gemfile
|
@ -2,7 +2,7 @@ source 'https://rubygems.org'
|
|||
|
||||
gemspec
|
||||
|
||||
gem 'mutant', :path => '.'
|
||||
gem 'mutant', path: '.'
|
||||
|
||||
gem 'devtools', :git => 'https://github.com/rom-rb/devtools.git'
|
||||
eval(File.read(File.join(File.dirname(__FILE__),'Gemfile.devtools')))
|
||||
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
|
||||
eval(File.read(File.join(File.dirname(__FILE__), 'Gemfile.devtools')))
|
||||
|
|
|
@ -14,26 +14,27 @@ group :guard do
|
|||
gem 'guard', '~> 1.8.1'
|
||||
gem 'guard-bundler', '~> 1.0.0'
|
||||
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
|
||||
gem 'listen', '~> 1.2.2'
|
||||
gem 'rb-fchange', '~> 0.0.6', :require => false
|
||||
gem 'rb-fsevent', '~> 0.9.3', :require => false
|
||||
gem 'rb-inotify', '~> 0.9.0', :require => false
|
||||
gem 'rb-fchange', '~> 0.0.6', require: false
|
||||
gem 'rb-fsevent', '~> 0.9.3', require: false
|
||||
gem 'rb-inotify', '~> 0.9.0', require: false
|
||||
|
||||
# notification handling
|
||||
gem 'libnotify', '~> 0.8.0', :require => false
|
||||
gem 'rb-notifu', '~> 0.0.4', :require => false
|
||||
gem 'terminal-notifier-guard', '~> 1.5.3', :require => false
|
||||
gem 'libnotify', '~> 0.8.0', require: false
|
||||
gem 'rb-notifu', '~> 0.0.4', require: false
|
||||
gem 'terminal-notifier-guard', '~> 1.5.3', require: false
|
||||
end
|
||||
|
||||
group :metrics do
|
||||
gem 'coveralls', '~> 0.6.7'
|
||||
gem 'flay', '~> 2.3.1'
|
||||
gem 'flog', '~> 4.1.1'
|
||||
gem 'reek', '~> 1.3.1', :git => 'https://github.com/troessner/reek.git'
|
||||
# gem 'rubocop', '~> 0.9.1'
|
||||
gem 'reek', '~> 1.3.1', git: 'https://github.com/troessner/reek.git'
|
||||
gem 'rubocop', '~> 0.10.0', git: 'https://github.com/bbatsov/rubocop.git'
|
||||
gem 'simplecov', '~> 0.7.1'
|
||||
gem 'yardstick', '~> 0.9.6'
|
||||
|
||||
|
@ -41,10 +42,6 @@ group :metrics do
|
|||
gem 'yard-spellcheck', '~> 0.1.5'
|
||||
end
|
||||
|
||||
platforms :ruby_19 do
|
||||
gem 'json', '~> 1.8.0'
|
||||
end
|
||||
|
||||
platforms :rbx do
|
||||
gem 'pelusa', '~> 0.2.2'
|
||||
end
|
||||
|
|
32
README.md
32
README.md
|
@ -5,7 +5,7 @@ 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)
|
||||
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
trap('INT') do |status|
|
||||
exit! 128+status
|
||||
exit! 128 + status
|
||||
end
|
||||
|
||||
require 'mutant'
|
||||
|
||||
namespace =
|
||||
if File.basename($0) == 'zombie'
|
||||
$stderr.puts('Detected zombie environment...')
|
||||
if ARGV.include?('--zombie')
|
||||
$stderr.puts('Running mutant zombified!')
|
||||
Mutant::Zombifier.zombify
|
||||
Zombie::Mutant
|
||||
else
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
mutant
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 16
|
||||
total_score: 685
|
||||
total_score: 737
|
||||
|
|
|
@ -47,6 +47,7 @@ NestedIterators:
|
|||
exclude:
|
||||
- Mutant#self.singleton_subclass_instance
|
||||
- Mutant::Mutator::Util::Array::Element#dispatch
|
||||
- Mutant::Reporter::CLI::Printer::Config::Runner#generic_stats
|
||||
- Mutant::CLI#parse
|
||||
max_allowed_nesting: 1
|
||||
ignore_iterators: []
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
AllCops:
|
||||
Includes:
|
||||
- '../**/*.rake'
|
||||
- 'Gemfile'
|
||||
- 'Gemfile.devtools'
|
||||
Excludes:
|
||||
- '../vendor/**'
|
||||
- '**/vendor/**'
|
||||
- '**/benchmarks/**'
|
||||
|
||||
# Avoid parameter lists longer than five parameters.
|
||||
ParameterLists:
|
||||
|
|
|
@ -20,7 +20,9 @@ require 'concord'
|
|||
|
||||
# Library namespace
|
||||
module Mutant
|
||||
end
|
||||
# The empty string used within this namespace
|
||||
EMPTY_STRING = ''.freeze
|
||||
end # Mutant
|
||||
|
||||
require 'mutant/cache'
|
||||
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/regex'
|
||||
require 'mutant/mutator/node/literal/nil'
|
||||
require 'mutant/mutator/node/assignment'
|
||||
require 'mutant/mutator/node/argument'
|
||||
require 'mutant/mutator/node/arguments'
|
||||
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/super'
|
||||
require 'mutant/mutator/node/zsuper'
|
||||
require 'mutant/mutator/node/send'
|
||||
require 'mutant/mutator/node/send/binary'
|
||||
require 'mutant/mutator/node/when'
|
||||
require 'mutant/mutator/node/assignment'
|
||||
require 'mutant/mutator/node/define'
|
||||
require 'mutant/mutator/node/mlhs'
|
||||
require 'mutant/mutator/node/masgn'
|
||||
|
|
|
@ -202,6 +202,8 @@ module Mutant
|
|||
opts.separator ''
|
||||
opts.separator 'Strategies:'
|
||||
|
||||
opts.on('--zombie', 'Run mutant zombified')
|
||||
|
||||
add_strategies(opts)
|
||||
add_options(opts)
|
||||
end
|
||||
|
|
|
@ -10,7 +10,8 @@ module Mutant
|
|||
OPERATOR_PATTERN = Regexp.union(*OPERATOR_METHODS.map(&:to_s)).freeze
|
||||
METHOD_NAME_PATTERN = /([_A-Za-z][A-Za-z0-9_]*[!?=]?|#{OPERATOR_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
|
||||
|
||||
REGISTRY = []
|
||||
|
@ -35,7 +36,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -71,7 +71,7 @@ module Mutant
|
|||
:kwarg, :restarg, :arg, :block_pass, :or, :and,
|
||||
:next, :undef, :if, :module, :cbase, :block, :send,
|
||||
: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,
|
||||
:regopt, :int, :str, :float, :sym, :pair, :hash, :array,
|
||||
:xstr, :def, :defs, :case, :when, :ivar, :lvar, :cvar, :gvar,
|
||||
|
|
|
@ -18,14 +18,21 @@ module Mutant
|
|||
def run
|
||||
mutation.insert
|
||||
# TODO: replace with real streams from configuration
|
||||
# Note: we assume the only interesting output from a failed rspec run is stderr.
|
||||
require 'stringio'
|
||||
null = StringIO.new
|
||||
argv = command_line_arguments
|
||||
begin
|
||||
!::RSpec::Core::Runner.run(argv, null, null).zero?
|
||||
rescue StandardError
|
||||
true
|
||||
rspec_err = StringIO.new
|
||||
|
||||
killed = !::RSpec::Core::Runner.run(command_line_arguments, nil, rspec_err).zero?
|
||||
|
||||
if killed and mutation.should_survive?
|
||||
rspec_err.rewind
|
||||
|
||||
puts "#{mutation.class} test failed."
|
||||
puts 'RSpec stderr:'
|
||||
puts rspec_err.read
|
||||
end
|
||||
|
||||
killed
|
||||
end
|
||||
|
||||
# Return command line arguments
|
||||
|
|
|
@ -28,6 +28,18 @@ module Mutant
|
|||
#
|
||||
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
|
||||
#
|
||||
# FIXME:
|
||||
|
|
|
@ -30,6 +30,16 @@ module Mutant
|
|||
killer.killed?
|
||||
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 # Mutation
|
||||
end # Mutant
|
||||
|
|
|
@ -10,6 +10,19 @@ module Mutant
|
|||
|
||||
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
|
||||
|
||||
# Return identification
|
||||
|
@ -39,6 +52,16 @@ module Mutant
|
|||
!killer.killed?
|
||||
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 # Mutation
|
||||
end # Mutant
|
||||
|
|
|
@ -21,7 +21,8 @@ module Mutant
|
|||
emit_self(*children)
|
||||
end
|
||||
end
|
||||
children.each do |child|
|
||||
children.each_with_index do |child, index|
|
||||
mutate_child(index)
|
||||
emit(child)
|
||||
end
|
||||
end
|
||||
|
|
25
lib/mutant/mutator/node/cbase.rb
Normal file
25
lib/mutant/mutator/node/cbase.rb
Normal 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
|
59
lib/mutant/mutator/node/connective/binary.rb
Normal file
59
lib/mutant/mutator/node/connective/binary.rb
Normal 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
|
28
lib/mutant/mutator/node/const.rb
Normal file
28
lib/mutant/mutator/node/const.rb
Normal 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
|
|
@ -1,20 +1,19 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class Node
|
||||
|
||||
# Generic mutator
|
||||
class Generic < self
|
||||
|
||||
handle(:self)
|
||||
|
||||
# These nodes still need a dedicated mutator,
|
||||
# your contribution is that close!
|
||||
handle(
|
||||
:zsuper, :not, :or, :and, :defined,
|
||||
:next, :break, :match, :gvar, :cvar, :ensure,
|
||||
:defined,
|
||||
:next, :break, :match, :ensure,
|
||||
:dstr, :dsym, :yield, :rescue, :redo, :defined?,
|
||||
:lvar, :const, :blockarg, :block_pass, :op_asgn, :and_asgn,
|
||||
:regopt, :ivar, :restarg, :casgn, :resbody, :retry, :arg_expr,
|
||||
:kwrestarg, :kwoptarg, :kwarg, :undef, :module, :cbase, :empty,
|
||||
:blockarg, :block_pass, :op_asgn, :and_asgn,
|
||||
:regopt, :restarg, :resbody, :retry, :arg_expr,
|
||||
:kwrestarg, :kwoptarg, :kwarg, :undef, :module, :empty,
|
||||
:alias, :for, :xstr, :back_ref, :nth_ref, :class,
|
||||
:sclass, :match_with_lvasgn, :match_current_line, :or_asgn, :kwbegin
|
||||
)
|
||||
|
@ -29,8 +28,7 @@ module Mutant
|
|||
#
|
||||
def dispatch
|
||||
children.each_with_index do |child, index|
|
||||
next unless child.kind_of?(Parser::AST::Node)
|
||||
mutate_child(index)
|
||||
mutate_child(index) if child.kind_of?(Parser::AST::Node)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@ module Mutant
|
|||
#
|
||||
def mutate_condition
|
||||
emit_condition_mutations
|
||||
emit_self(s(:send, condition, :!), if_branch, else_branch)
|
||||
emit_self(s(:true), if_branch, else_branch)
|
||||
emit_self(s(:false), if_branch, else_branch)
|
||||
emit_self(n_not(condition), if_branch, else_branch)
|
||||
emit_self(N_TRUE, if_branch, else_branch)
|
||||
emit_self(N_FALSE, if_branch, else_branch)
|
||||
end
|
||||
|
||||
# Emit if branch mutations
|
||||
|
|
|
@ -7,15 +7,21 @@ module Mutant
|
|||
|
||||
handle(:regexp)
|
||||
|
||||
EMPTY_STRING = ''.freeze
|
||||
|
||||
# No input can ever be matched with this
|
||||
NULL_REGEXP_SOURCE = 'a\A'.freeze
|
||||
|
||||
children :source, :options
|
||||
|
||||
private
|
||||
|
||||
# Return options
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def options
|
||||
children.last
|
||||
end
|
||||
|
||||
# Emit mutants
|
||||
#
|
||||
# @return [undefined]
|
||||
|
@ -24,6 +30,9 @@ module Mutant
|
|||
#
|
||||
def dispatch
|
||||
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, NULL_REGEXP_SOURCE), options)
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
class Mutator
|
||||
class Node
|
||||
|
||||
# Mutation emitter to handle multipl assignment nodes
|
||||
# Mutation emitter to handle multiple assignment nodes
|
||||
class MultipleAssignment < self
|
||||
|
||||
handle(:masgn)
|
||||
|
|
27
lib/mutant/mutator/node/named_value/access.rb
Normal file
27
lib/mutant/mutator/node/named_value/access.rb
Normal 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
|
42
lib/mutant/mutator/node/named_value/constant_assignment.rb
Normal file
42
lib/mutant/mutator/node/named_value/constant_assignment.rb
Normal 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
|
|
@ -1,11 +1,10 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class Node
|
||||
# Mutator base class for assignments
|
||||
class Assignment < self
|
||||
module NamedValue
|
||||
|
||||
# Mutator for variable assignment
|
||||
class Variable < self
|
||||
# Mutation emitter to handle variable assignment nodes
|
||||
class VariableAssignment < Node
|
||||
|
||||
children :name, :value
|
||||
|
||||
|
@ -40,12 +39,12 @@ module Mutant
|
|||
def mutate_name
|
||||
prefix = MAP.fetch(node.type)
|
||||
Mutator::Util::Symbol.each(name, self) do |name|
|
||||
emit_name("#{prefix}#{name}")
|
||||
emit_name(prefix + name.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
end # Variable
|
||||
end # Assignment
|
||||
end # VariableAssignment
|
||||
end # NamedValue
|
||||
end # Node
|
||||
end # Mutator
|
||||
end # Mutant
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
class Mutator
|
||||
class Node
|
||||
|
||||
# Mutator for super with parantheses
|
||||
# Mutator for super with parentheses
|
||||
class Super < self
|
||||
|
||||
handle(:super)
|
||||
|
|
25
lib/mutant/mutator/node/zsuper.rb
Normal file
25
lib/mutant/mutator/node/zsuper.rb
Normal 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
|
|
@ -15,7 +15,6 @@ module Mutant
|
|||
end
|
||||
module_function :s
|
||||
|
||||
|
||||
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)))
|
||||
INFINITY = s(:send, s(:float, 1.0), :/, s(:args, s(:float, 0.0)))
|
||||
|
@ -23,8 +22,22 @@ module Mutant
|
|||
|
||||
RAISE = s(:send, nil, :raise)
|
||||
|
||||
N_TRUE = s(:true)
|
||||
N_FALSE = s(:false)
|
||||
N_NIL = s(:nil)
|
||||
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 # Mutant
|
||||
|
|
|
@ -45,6 +45,7 @@ module Mutant
|
|||
info 'Overhead: %0.2f%%', overhead
|
||||
status 'Coverage: %0.2f%%', coverage
|
||||
status 'Alive: %s', amount_alive
|
||||
print_generic_stats
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -60,6 +61,81 @@ module Mutant
|
|||
object.subjects
|
||||
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 [Fixnum]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = 'mutant'
|
||||
gem.version = '0.3.0.beta17'
|
||||
gem.version = '0.3.0.beta21'
|
||||
gem.authors = [ 'Markus Schirp' ]
|
||||
gem.email = [ 'mbj@schirp-dso.com' ]
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ RSpec.configure do |config|
|
|||
config.include(CompressHelper)
|
||||
config.include(ParserHelper)
|
||||
config.include(Mutant::NodeHelpers)
|
||||
config.mock_with :rspec do |config|
|
||||
config.syntax = [:expect, :should]
|
||||
config.mock_with :rspec do |rspec|
|
||||
rspec.syntax = [:expect, :should]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ describe Mutant::Killer::Rspec, '.new' do
|
|||
|
||||
let(:strategy) { double('Strategy', :spec_files => ['foo'], :error_stream => $stderr, :output_stream => $stdout) }
|
||||
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(:object) { described_class }
|
||||
|
|
17
spec/unit/mutant/mutator/node/cbase/mutation_spec.rb
Normal file
17
spec/unit/mutant/mutator/node/cbase/mutation_spec.rb
Normal 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
|
|
@ -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
|
18
spec/unit/mutant/mutator/node/const/mutation_spec.rb
Normal file
18
spec/unit/mutant/mutator/node/const/mutation_spec.rb
Normal 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
|
|
@ -2,6 +2,7 @@ require 'spec_helper'
|
|||
|
||||
describe Mutant::Mutator::Node::Literal, 'regex' do
|
||||
|
||||
context 'literal' do
|
||||
let(:source) { '/foo/' }
|
||||
|
||||
let(:mutations) do
|
||||
|
@ -12,4 +13,20 @@ describe Mutant::Mutator::Node::Literal, 'regex' do
|
|||
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
|
||||
|
|
|
@ -6,7 +6,6 @@ describe Mutant::Mutator, 'masgn' do
|
|||
Mutant::Random.stub(:hex_string => 'random')
|
||||
end
|
||||
|
||||
|
||||
let(:source) { 'a, b = c, d' }
|
||||
|
||||
let(:mutations) do
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Mutator::Node::Assignment, 'mutations' do
|
||||
describe Mutant::Mutator::Node::NamedValue::VariableAssignment, 'mutations' do
|
||||
|
||||
before do
|
||||
Mutant::Random.stub(:hex_string => :random)
|
|
@ -9,6 +9,7 @@ describe Mutant::Mutator, 'send' do
|
|||
let(:mutations) do
|
||||
mutations = []
|
||||
mutations << 'foo ||= expression'
|
||||
mutations << 'nil.foo ||= expression'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
|
@ -74,6 +75,7 @@ describe Mutant::Mutator, 'send' do
|
|||
mutations = []
|
||||
mutations << 'foo'
|
||||
mutations << 'self'
|
||||
mutations << 'nil.foo'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
|
@ -111,6 +113,7 @@ describe Mutant::Mutator, 'send' do
|
|||
mutations = []
|
||||
mutations << 'self.class'
|
||||
mutations << 'self.foo'
|
||||
mutations << 'nil.class.foo'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
|
@ -142,6 +145,7 @@ describe Mutant::Mutator, 'send' do
|
|||
mutations << 'foo(nil)'
|
||||
mutations << 'nil'
|
||||
mutations << 'self.foo(::Object.new)'
|
||||
mutations << 'nil.foo(nil)'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
|
@ -187,6 +191,8 @@ describe Mutant::Mutator, 'send' do
|
|||
mutations = []
|
||||
mutations << 'foo'
|
||||
mutations << 'left - right'
|
||||
mutations << 'left / foo'
|
||||
mutations << 'right / foo'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
|
|
|
@ -5,7 +5,12 @@ describe Mutant::Mutator, 'super' do
|
|||
context 'with no arguments' do
|
||||
let(:source) { 'super' }
|
||||
|
||||
it_should_behave_like 'a noop mutator'
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
mutations << 'nil'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
end
|
||||
|
||||
context 'with explicit empty arguments' do
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Mutator, 'self' do
|
||||
let(:source) { 'self' }
|
||||
|
||||
it_should_behave_like 'a noop mutator'
|
||||
end
|
12
spec/unit/mutant/node_helpers/n_not_spec.rb
Normal file
12
spec/unit/mutant/node_helpers/n_not_spec.rb
Normal 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
|
|
@ -30,7 +30,10 @@ shared_examples_for 'a mutator' do
|
|||
|
||||
unless subject == expected_mutations
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,4 +6,6 @@ require 'test_app'
|
|||
require 'rspec'
|
||||
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe TestApp::Literal,'#string' do
|
||||
describe TestApp::Literal, '#string' do
|
||||
subject { object.string }
|
||||
|
||||
let(:object) { described_class.new }
|
||||
|
|
Loading…
Reference in a new issue