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
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')))

View file

@ -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

View file

@ -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
--------

View file

@ -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

View file

@ -1 +0,0 @@
mutant

View file

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

View file

@ -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: []

View file

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

View file

@ -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'

View file

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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

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
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

View file

@ -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
@ -45,7 +45,7 @@ module Mutant
emit_self(condition, else_branch, nil) if else_branch
if if_branch
emit_if_branch_mutations
emit_self(condition, if_branch, nil)
emit_self(condition, if_branch, nil)
end
end

View file

@ -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

View file

@ -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)

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
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

View file

@ -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)

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
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

View file

@ -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]

View file

@ -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' ]

View file

@ -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

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(: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 }

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
let(:source) { '/foo/' }
context 'literal' do
let(:source) { '/foo/' }
let(:mutations) do
mutations = []
mutations << 'nil'
mutations << '//' # match all
mutations << '/a\A/' # match nothing
let(:mutations) do
mutations = []
mutations << 'nil'
mutations << '//' # match all
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
it_should_behave_like 'a mutator'
end

View file

@ -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

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'
describe Mutant::Mutator::Node::Assignment, 'mutations' do
describe Mutant::Mutator::Node::NamedValue::VariableAssignment, 'mutations' do
before do
Mutant::Random.stub(:hex_string => :random)

View file

@ -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'

View file

@ -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

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
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

View file

@ -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

View file

@ -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 }