commit
f3e1a76914
27 changed files with 295 additions and 647 deletions
2
Gemfile
2
Gemfile
|
@ -6,6 +6,8 @@ gemspec
|
|||
|
||||
gem 'mutant', path: '.'
|
||||
|
||||
gem 'rspec-core', path: '../rspec-core'
|
||||
|
||||
group :development, :test do
|
||||
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
|
||||
end
|
||||
|
|
|
@ -17,10 +17,7 @@ any ruby engine that supports POSIX-fork(2) semantics.
|
|||
|
||||
Only rspec2 is supported currently. This is subject to change.
|
||||
|
||||
It is easy to write a mutation killer for other test/spec frameworks than rspec2.
|
||||
Just create your own Mutant::Killer subclass, and make sure I get a PR!
|
||||
|
||||
See this [ASCII-Cast](http://ascii.io/a/1707) for mutant in action! (v0.2.1)
|
||||
It is easy to write a mutation killer/strategy for other test/spec frameworks than rspec2.
|
||||
|
||||
Projects using Mutant
|
||||
---------------------
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 16
|
||||
total_score: 758
|
||||
total_score: 734
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
---
|
||||
threshold: 20.5
|
||||
threshold: 20.0
|
||||
|
|
|
@ -96,8 +96,7 @@ UncommunicativeMethodName:
|
|||
accept: []
|
||||
UncommunicativeModuleName:
|
||||
enabled: true
|
||||
exclude:
|
||||
- Mutant::Strategy::Rspec::DM2
|
||||
exclude: []
|
||||
reject:
|
||||
- !ruby/regexp /^.$/
|
||||
- !ruby/regexp /[0-9]$/
|
||||
|
|
|
@ -19,6 +19,7 @@ require 'diff/lcs/hunk'
|
|||
require 'rspec'
|
||||
require 'anima'
|
||||
require 'concord'
|
||||
require 'rspec'
|
||||
|
||||
# Library namespace
|
||||
module Mutant
|
||||
|
@ -105,11 +106,7 @@ require 'mutant/killer/forking'
|
|||
require 'mutant/killer/forked'
|
||||
require 'mutant/strategy'
|
||||
require 'mutant/strategy/static'
|
||||
require 'mutant/strategy/method_expansion'
|
||||
require 'mutant/strategy/rspec'
|
||||
require 'mutant/strategy/rspec/dm2'
|
||||
require 'mutant/strategy/rspec/dm2/lookup'
|
||||
require 'mutant/strategy/rspec/dm2/lookup/method'
|
||||
require 'mutant/runner'
|
||||
require 'mutant/runner/config'
|
||||
require 'mutant/runner/subject'
|
||||
|
|
|
@ -59,6 +59,7 @@ module Mutant
|
|||
def config
|
||||
Config.new(
|
||||
:cache => @cache,
|
||||
:zombie => @zombie,
|
||||
:debug => debug?,
|
||||
:matcher => matcher,
|
||||
:filter => filter,
|
||||
|
@ -154,26 +155,6 @@ module Mutant
|
|||
@filters << klass.new(filter)
|
||||
end
|
||||
|
||||
# Set fail fast
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def set_fail_fast
|
||||
@fail_fast = true
|
||||
end
|
||||
|
||||
# Set debug mode
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def set_debug
|
||||
@debug = true
|
||||
end
|
||||
|
||||
# Set strategy
|
||||
#
|
||||
# @param [Strategy] strategy
|
||||
|
@ -204,9 +185,8 @@ module Mutant
|
|||
builder.separator ''
|
||||
builder.separator 'Strategies:'
|
||||
|
||||
builder.on('--zombie', 'Run mutant zombified')
|
||||
|
||||
add_strategies(builder)
|
||||
add_environmental_options(builder)
|
||||
add_options(builder)
|
||||
end
|
||||
|
||||
|
@ -237,25 +217,48 @@ module Mutant
|
|||
|
||||
# Add strategies
|
||||
#
|
||||
# @param [Object]
|
||||
# @param [Object] opts
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def add_strategies(opts)
|
||||
opts.on('--rspec-unit', 'executes all specs under ./spec/unit') do
|
||||
set_strategy Strategy::Rspec::Unit
|
||||
end.on('--rspec-full', 'executes all specs under ./spec') do
|
||||
set_strategy Strategy::Rspec::Full
|
||||
end.on('--rspec-dm2', 'executes spec/unit/$nesting/$method_spec.rb') do
|
||||
set_strategy Strategy::Rspec::DM2
|
||||
opts.separator ''
|
||||
opts.separator 'Strategies:'
|
||||
|
||||
opts.on('--static-success', 'does succeed on all mutations') do
|
||||
set_strategy Strategy::Static::Success.new
|
||||
end
|
||||
opts.on('--static-fail', 'does fail on all mutations') do
|
||||
set_strategy Strategy::Static::Fail.new
|
||||
end
|
||||
opts.on('--rspec', 'kills mutations with rspec') do
|
||||
set_strategy Strategy::Rspec.new
|
||||
end
|
||||
end
|
||||
|
||||
# Add environmental options
|
||||
#
|
||||
# @param [Object] opts
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def add_environmental_options(opts)
|
||||
opts.on('--zombie', 'Run mutant zombified') do
|
||||
@zombie = true
|
||||
end.on('-I', 'Add directory to $LOAD_PATH') do |directory|
|
||||
$LOAD_PATH << directory
|
||||
end.on('-r', '--require NAME', 'Require file with NAME') do |name|
|
||||
require name
|
||||
end
|
||||
end
|
||||
|
||||
# Add options
|
||||
#
|
||||
# @param [Object]
|
||||
# @param [Object] opts
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
|
@ -266,18 +269,16 @@ module Mutant
|
|||
opts.separator 'Options:'
|
||||
|
||||
opts.on('--version', 'Print mutants version') do |name|
|
||||
puts "mutant-#{Mutant::VERSION}"
|
||||
puts("mutant-#{Mutant::VERSION}")
|
||||
Kernel.exit(0)
|
||||
end.on('-r', '--require NAME', 'Require file with NAME') do |name|
|
||||
require name
|
||||
end.on('--code FILTER', 'Adds a code filter') do |filter|
|
||||
add_filter Mutation::Filter::Code, filter
|
||||
add_filter(Mutation::Filter::Code, filter)
|
||||
end.on('--fail-fast', 'Fail fast') do
|
||||
set_fail_fast
|
||||
@fail_fast = true
|
||||
end.on('-d', '--debug', 'Enable debugging output') do
|
||||
set_debug
|
||||
@debug = true
|
||||
end.on_tail('-h', '--help', 'Show this message') do
|
||||
puts opts
|
||||
puts(opts)
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,8 @@ module Mutant
|
|||
# The configuration of a mutator run
|
||||
class Config
|
||||
include Adamantium::Flat, Anima.new(
|
||||
:cache, :debug, :strategy, :matcher, :filter, :reporter, :fail_fast
|
||||
:cache, :debug, :strategy, :matcher, :filter,
|
||||
:reporter, :fail_fast, :zombie
|
||||
)
|
||||
|
||||
# Enumerate subjects
|
||||
|
|
|
@ -89,6 +89,20 @@ module Mutant
|
|||
scope.name
|
||||
end
|
||||
|
||||
# Return match prefixes
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match_prefixes
|
||||
names = name_nesting
|
||||
names.length.downto(1).map do |last|
|
||||
names[0...last].join('::')
|
||||
end
|
||||
end
|
||||
memoize :match_prefixes
|
||||
|
||||
# Return scope wrapped by context
|
||||
#
|
||||
# @return [::Module|::Class]
|
||||
|
|
|
@ -98,6 +98,16 @@ module Mutant
|
|||
@runtime = times.real
|
||||
end
|
||||
|
||||
# Return subject
|
||||
#
|
||||
# @return [Subject]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def subject
|
||||
mutation.subject
|
||||
end
|
||||
|
||||
# Run killer
|
||||
#
|
||||
# @return [true]
|
||||
|
|
|
@ -5,6 +5,20 @@ module Mutant
|
|||
# Runner for rspec tests
|
||||
class Rspec < self
|
||||
|
||||
# Noop reporter
|
||||
module Reporter
|
||||
%w(example example_group).each do |method_name|
|
||||
%w(passed started failed finished pending).each do |state|
|
||||
name = "#{method_name}_#{state}"
|
||||
define_singleton_method(name) do |_subject|
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
freeze
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Run rspec test
|
||||
|
@ -19,36 +33,68 @@ module Mutant
|
|||
#
|
||||
def run
|
||||
mutation.insert
|
||||
# TODO: replace with real streams from configuration
|
||||
require 'stringio'
|
||||
# Note: We assume interesting output from a failed rspec run is stderr.
|
||||
rspec_err = StringIO.new
|
||||
|
||||
exit_code = ::RSpec::Core::Runner.run(cli_arguments, nil, rspec_err)
|
||||
groups = example_groups
|
||||
|
||||
killed = !exit_code.zero?
|
||||
|
||||
if killed and mutation.should_survive?
|
||||
rspec_err.rewind
|
||||
|
||||
puts "#{mutation.class} test failed."
|
||||
puts 'RSpec stderr:'
|
||||
puts rspec_err.read
|
||||
unless groups
|
||||
$stderr.puts "No rspec example groups found for: #{match_prefixes.join(', ')}"
|
||||
return false
|
||||
end
|
||||
|
||||
killed
|
||||
example_groups.each do |group|
|
||||
return true unless group.run(Reporter)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Return command line arguments
|
||||
# Return match prefixes
|
||||
#
|
||||
# @return [Array]
|
||||
# @return [Enumerble<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def cli_arguments
|
||||
%W(
|
||||
--fail-fast
|
||||
) + strategy.spec_files(mutation.subject)
|
||||
def match_prefixes
|
||||
subject.match_prefixes
|
||||
end
|
||||
|
||||
# Return example groups
|
||||
#
|
||||
# @return [Array<RSpec::Example>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def example_groups
|
||||
match_prefixes.each do |match_expression|
|
||||
example_groups = find_with(match_expression)
|
||||
return example_groups unless example_groups.empty?
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Return example groups that match expression
|
||||
#
|
||||
# @param [String] match_expression
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def find_with(match_expression)
|
||||
all_example_groups.select do |example_group|
|
||||
example_group.description.start_with?(match_expression)
|
||||
end
|
||||
end
|
||||
|
||||
# Return all example groups
|
||||
#
|
||||
# @return [Enumerable<RSpec::Example>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def all_example_groups
|
||||
strategy.example_groups
|
||||
end
|
||||
|
||||
end # Rspec
|
||||
|
|
|
@ -10,6 +10,9 @@ module Mutant
|
|||
|
||||
handle(Mutant::Killer::Forked)
|
||||
|
||||
SUCCESS = '.'.freeze
|
||||
FAILURE = 'F'.freeze
|
||||
|
||||
# Run printer
|
||||
#
|
||||
# @return [undefined]
|
||||
|
@ -18,12 +21,14 @@ module Mutant
|
|||
#
|
||||
def run
|
||||
if success?
|
||||
char('.', Color::GREEN)
|
||||
return
|
||||
char(SUCCESS, Color::GREEN)
|
||||
else
|
||||
char(FAILURE, Color::RED)
|
||||
end
|
||||
char('F', Color::RED)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Write colorized char
|
||||
#
|
||||
# @param [String] char
|
||||
|
@ -37,6 +42,7 @@ module Mutant
|
|||
output.write(colorize(color, char))
|
||||
output.flush
|
||||
end
|
||||
|
||||
end # Killer
|
||||
end # Printer
|
||||
end # CLI
|
||||
|
|
|
@ -6,24 +6,22 @@ module Mutant
|
|||
class Strategy
|
||||
include AbstractType, Adamantium::Flat
|
||||
|
||||
# Perform setup
|
||||
# Perform strategy setup
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.setup
|
||||
self
|
||||
def setup
|
||||
end
|
||||
|
||||
# Perform teardown
|
||||
# Perform strategy teardown
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.teardown
|
||||
self
|
||||
def teardown
|
||||
end
|
||||
|
||||
# Kill mutation
|
||||
|
@ -34,18 +32,20 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.kill(mutation)
|
||||
def kill(mutation)
|
||||
killer.new(self, mutation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return killer
|
||||
#
|
||||
# @return [Class:Killer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.killer
|
||||
self::KILLER
|
||||
def killer
|
||||
self.class::KILLER
|
||||
end
|
||||
|
||||
end # Strategy
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Strategy
|
||||
module MethodExpansion
|
||||
|
||||
# Run method name expansion
|
||||
#
|
||||
# @param [Symbol] name
|
||||
#
|
||||
# @return [Symbol]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(name)
|
||||
name = map(name) || expand(name)
|
||||
end
|
||||
|
||||
# Return mapped name
|
||||
#
|
||||
# @param [Symbol] name
|
||||
#
|
||||
# @return [Symbol]
|
||||
# if name was mapped
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.map(name)
|
||||
OPERATOR_EXPANSIONS[name]
|
||||
end
|
||||
private_class_method :map
|
||||
|
||||
REGEXP = /#{Regexp.union(*METHOD_POSTFIX_EXPANSIONS.keys)}\z/.freeze
|
||||
|
||||
# Return expanded name
|
||||
#
|
||||
# @param [Symbol] name
|
||||
#
|
||||
# @return [Symbol]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.expand(name)
|
||||
name.to_s.gsub(REGEXP, METHOD_POSTFIX_EXPANSIONS).to_sym
|
||||
end
|
||||
private_class_method :expand
|
||||
|
||||
end # MethodExpansion
|
||||
end # Strategy
|
||||
end # Mutant
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
module Mutant
|
||||
class Strategy
|
||||
|
||||
# Rspec strategy base class
|
||||
# Rspec killer strategy
|
||||
class Rspec < self
|
||||
include Equalizer.new
|
||||
|
||||
KILLER = Killer::Forking.new(Killer::Rspec)
|
||||
|
||||
|
@ -14,52 +14,62 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.setup
|
||||
require('./spec/spec_helper.rb')
|
||||
def setup
|
||||
output = StringIO.new
|
||||
configuration.error_stream = output
|
||||
configuration.output_stream = output
|
||||
options.configure(configuration)
|
||||
configuration.load_spec_files
|
||||
self
|
||||
end
|
||||
memoize :setup
|
||||
|
||||
# Run all unit specs per mutation
|
||||
class Unit < self
|
||||
# Return configuration
|
||||
#
|
||||
# @return [RSpec::Core::Configuration]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def configuration
|
||||
RSpec::Core::Configuration.new
|
||||
end
|
||||
memoize :configuration, :freezer => :noop
|
||||
|
||||
# Return file name pattern for mutation
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.spec_files(_mutation)
|
||||
Dir['spec/unit/**/*_spec.rb']
|
||||
end
|
||||
end # Unit
|
||||
# Return example groups
|
||||
#
|
||||
# @return [Enumerable<RSpec::Core::ExampleGroup>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def example_groups
|
||||
world.example_groups
|
||||
end
|
||||
|
||||
# Run all integration specs per mutation
|
||||
class Integration < self
|
||||
private
|
||||
|
||||
# Return file name pattern for mutation
|
||||
#
|
||||
# @return [Mutation]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.spec_files(_mutation)
|
||||
Dir['spec/integration/**/*_spec.rb']
|
||||
end
|
||||
end # Integration
|
||||
# Return world
|
||||
#
|
||||
# @return [RSpec::Core::World]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def world
|
||||
RSpec.world
|
||||
end
|
||||
memoize :world, :freezer => :noop
|
||||
|
||||
# Run all specs per mutation
|
||||
class Full < self
|
||||
|
||||
# Return spec files
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.spec_files(_mutation)
|
||||
Dir['spec/**/*_spec.rb']
|
||||
end
|
||||
end # Full
|
||||
# Return options
|
||||
#
|
||||
# @return [RSpec::Core::ConfigurationOptions]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def options
|
||||
options = RSpec::Core::ConfigurationOptions.new(%w(--fail-fast spec))
|
||||
options.parse_options
|
||||
options
|
||||
end
|
||||
memoize :options, :freezer => :noop
|
||||
|
||||
end # Rspec
|
||||
end # Strategy
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Strategy
|
||||
class Rspec
|
||||
# DM2-style strategy
|
||||
class DM2 < self
|
||||
|
||||
# Return filename pattern
|
||||
#
|
||||
# @param [Subject] subject
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.spec_files(subject)
|
||||
Lookup.run(subject)
|
||||
end
|
||||
|
||||
end # DM2
|
||||
end # Rspec
|
||||
end # Strategy
|
||||
end # Mutant
|
|
@ -1,63 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Strategy
|
||||
class Rspec
|
||||
class DM2
|
||||
|
||||
# Example lookup for the rspec dm2
|
||||
class Lookup
|
||||
include AbstractType, Adamantium::Flat, Concord::Public.new(:subject)
|
||||
|
||||
# Return glob expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :spec_files
|
||||
|
||||
# Perform example lookup
|
||||
#
|
||||
# @param [Subject] subject
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(subject)
|
||||
build(subject).spec_files
|
||||
end
|
||||
|
||||
REGISTRY = {}
|
||||
|
||||
# Register subject hander
|
||||
#
|
||||
# @param [Class:Subject]
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.handle(subject_class)
|
||||
REGISTRY[subject_class] = self
|
||||
end
|
||||
private_class_method :handle
|
||||
|
||||
# Build lookup object
|
||||
#
|
||||
# @param [Subject] subject
|
||||
#
|
||||
# @return [Lookup]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(subject)
|
||||
REGISTRY.fetch(subject.class).new(subject)
|
||||
end
|
||||
|
||||
end # Lookup
|
||||
end # DM2
|
||||
end # Rspec
|
||||
end # Strategy
|
||||
end # Mutant
|
|
@ -1,145 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Strategy
|
||||
class Rspec
|
||||
class DM2
|
||||
class Lookup
|
||||
|
||||
# Base class for dm2 style method lookup
|
||||
class Method < self
|
||||
|
||||
# Return spec files
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def spec_files
|
||||
Dir.glob(glob_expression)
|
||||
end
|
||||
memoize :spec_files
|
||||
|
||||
private
|
||||
|
||||
# Return base path
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def base_path
|
||||
"spec/unit/#{Inflecto.underscore(subject.context.name)}"
|
||||
end
|
||||
|
||||
# Return method name
|
||||
#
|
||||
# @return [Symbol]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def method_name
|
||||
subject.name
|
||||
end
|
||||
|
||||
# Test if method is public
|
||||
#
|
||||
# @return [true]
|
||||
# if method is public
|
||||
#
|
||||
# @return [false]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def public?
|
||||
subject.public?
|
||||
end
|
||||
|
||||
# Return expanded name
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def expanded_name
|
||||
MethodExpansion.run(method_name)
|
||||
end
|
||||
|
||||
# Return glob expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def glob_expression
|
||||
public? ? public_glob_expression : private_glob_expression
|
||||
end
|
||||
|
||||
# Return public glob expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def public_glob_expression
|
||||
"#{base_path}/#{expanded_name}_spec.rb"
|
||||
end
|
||||
|
||||
# Return private glob expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def private_glob_expression
|
||||
"#{base_path}/*_spec.rb"
|
||||
end
|
||||
|
||||
# Instance method dm2 style method lookup
|
||||
class Instance < self
|
||||
handle(Subject::Method::Instance)
|
||||
handle(Subject::Method::Instance::Memoized)
|
||||
|
||||
private
|
||||
|
||||
# Return glob expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def glob_expression
|
||||
glob_expression = super
|
||||
if method_name == :initialize and !public?
|
||||
"{#{glob_expression},#{base_path}/class_methods/new_spec.rb}"
|
||||
else
|
||||
glob_expression
|
||||
end
|
||||
end
|
||||
|
||||
end # Instance
|
||||
|
||||
# Singleton method dm2 style method lookup
|
||||
class Singleton < self
|
||||
handle(Subject::Method::Singleton)
|
||||
|
||||
private
|
||||
|
||||
# Return base path
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def base_path
|
||||
"#{super}/class_methods"
|
||||
end
|
||||
|
||||
end # Singleton
|
||||
|
||||
end # Method
|
||||
end # Lookup
|
||||
end # DM2
|
||||
end # Rspec
|
||||
end # Strategy
|
||||
end # Mutant
|
|
@ -46,7 +46,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def identification
|
||||
"#{subtype}:#{source_path}:#{source_line}"
|
||||
"#{match_expression}:#{source_path}:#{source_line}"
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
|
@ -84,16 +84,26 @@ module Mutant
|
|||
end
|
||||
memoize :original_root
|
||||
|
||||
private
|
||||
|
||||
# Return subtype identifier
|
||||
# Return match expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :subtype
|
||||
private :subtype
|
||||
abstract_method :match_expression
|
||||
|
||||
# Return match prefixes
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match_prefixes
|
||||
[match_expression].concat(context.match_prefixes)
|
||||
end
|
||||
memoize :match_prefixes
|
||||
|
||||
private
|
||||
|
||||
# Return neutral mutation
|
||||
#
|
||||
|
|
|
@ -27,6 +27,16 @@ module Mutant
|
|||
node.children[self.class::NAME_INDEX]
|
||||
end
|
||||
|
||||
# Return match expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match_expression
|
||||
"#{context.identification}#{self.class::SYMBOL}#{name}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return mutations
|
||||
|
@ -54,16 +64,6 @@ module Mutant
|
|||
context.scope
|
||||
end
|
||||
|
||||
# Return subtype identifier
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def subtype
|
||||
"#{context.identification}#{self.class::SYMBOL}#{name}"
|
||||
end
|
||||
|
||||
end # Method
|
||||
end # Subject
|
||||
end # Mutant
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant, 'rspec integration' do
|
||||
|
||||
around do |example|
|
||||
Dir.chdir(TestApp.root) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
let(:strategy) { Mutant::Strategy::Rspec::DM2 }
|
||||
|
||||
specify 'allows to kill mutations' do
|
||||
cli = 'bundle exec mutant --rspec-dm2 ::TestApp::Literal#string'
|
||||
Kernel.system(cli).should be(true)
|
||||
end
|
||||
|
||||
specify 'fails to kill mutations when they are not covered' do
|
||||
cli = 'bundle exec mutant --rspec-dm2 ::TestApp::Literal#uncovered_string'
|
||||
Kernel.system(cli).should be(false)
|
||||
end
|
||||
|
||||
specify 'fails when some mutations when are not covered' do
|
||||
cli = 'bundle exec mutant --rspec-dm2 ::TestApp::Literal'
|
||||
Kernel.system(cli).should be(false)
|
||||
end
|
||||
end
|
26
spec/integration/mutant/rspec_spec.rb
Normal file
26
spec/integration/mutant/rspec_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant, 'rspec integration' do
|
||||
|
||||
around do |example|
|
||||
Dir.chdir(TestApp.root) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
specify 'it allows to kill mutations' do
|
||||
Kernel.system('bundle exec mutant --rspec ::TestApp::Literal#string').should be(true)
|
||||
end
|
||||
|
||||
pending 'fails to kill mutations when they are not covered' do
|
||||
cli = 'bundle exec mutant --rspec ::TestApp::Literal#uncovered_string'
|
||||
Kernel.system(cli).should be(false)
|
||||
end
|
||||
|
||||
pending 'fails when some mutations when are not covered' do
|
||||
cli = 'bundle exec mutant --rspec ::TestApp::Literal'
|
||||
Kernel.system(cli).should be(false)
|
||||
end
|
||||
end
|
|
@ -29,7 +29,7 @@ describe Mutant::CLI, '.new' do
|
|||
|
||||
# Defaults
|
||||
let(:expected_filter) { Mutant::Mutation::Filter::ALL }
|
||||
let(:expected_strategy) { Mutant::Strategy::Rspec::Unit }
|
||||
let(:expected_strategy) { Mutant::Strategy::Rspec.new }
|
||||
let(:expected_reporter) { Mutant::Reporter::CLI.new($stdout) }
|
||||
|
||||
let(:ns) { Mutant::CLI::Classifier }
|
||||
|
@ -56,9 +56,10 @@ describe Mutant::CLI, '.new' do
|
|||
end
|
||||
|
||||
context 'with many strategy flags' do
|
||||
let(:arguments) { %w(--rspec-unit --rspec-dm2) }
|
||||
let(:arguments) { %w(--static-fail --rspec TestApp) }
|
||||
let(:expected_matcher) { Mutant::CLI::Classifier::Namespace::Flat.new(Mutant::Cache.new, 'TestApp') }
|
||||
|
||||
let(:expected_strategy) { Mutant::Strategy::Rspec::DM2 }
|
||||
it_should_behave_like 'a cli parser'
|
||||
end
|
||||
|
||||
context 'without arguments' do
|
||||
|
@ -70,30 +71,54 @@ describe Mutant::CLI, '.new' do
|
|||
end
|
||||
|
||||
context 'with code filter and missing argument' do
|
||||
let(:arguments) { %w(--rspec-unit --code) }
|
||||
let(:arguments) { %w(--rspec --code) }
|
||||
let(:expected_message) { 'missing argument: --code' }
|
||||
|
||||
it_should_behave_like 'an invalid cli run'
|
||||
end
|
||||
|
||||
context 'with explicit method matcher' do
|
||||
let(:arguments) { %w(--rspec-unit TestApp::Literal#float) }
|
||||
let(:arguments) { %w(--rspec TestApp::Literal#float) }
|
||||
let(:expected_matcher) { ns::Method.new(cache, 'TestApp::Literal#float') }
|
||||
|
||||
it_should_behave_like 'a cli parser'
|
||||
end
|
||||
|
||||
context 'with debug flag' do
|
||||
let(:matcher) { '::TestApp*' }
|
||||
let(:arguments) { %W(--debug --rspec #{matcher}) }
|
||||
let(:expected_matcher) { ns::Namespace::Recursive.new(cache, matcher) }
|
||||
|
||||
it_should_behave_like 'a cli parser'
|
||||
|
||||
it 'should set the debug option' do
|
||||
subject.config.debug.should be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with zombie flag' do
|
||||
let(:matcher) { '::TestApp*' }
|
||||
let(:arguments) { %W(--zombie --rspec #{matcher}) }
|
||||
let(:expected_matcher) { ns::Namespace::Recursive.new(cache, matcher) }
|
||||
|
||||
it_should_behave_like 'a cli parser'
|
||||
|
||||
it 'should set the zombie option' do
|
||||
subject.config.zombie.should be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with namespace matcher' do
|
||||
let(:matcher) { '::TestApp*' }
|
||||
let(:arguments) { %W(--rspec-unit #{matcher}) }
|
||||
let(:arguments) { %W(--rspec #{matcher}) }
|
||||
let(:expected_matcher) { ns::Namespace::Recursive.new(cache, matcher) }
|
||||
|
||||
it_should_behave_like 'a cli parser'
|
||||
end
|
||||
|
||||
context 'with code filter' do
|
||||
let(:matcher) { 'TestApp::Literal#float' }
|
||||
let(:arguments) { %W(--rspec-unit --code faa --code bbb #{matcher}) }
|
||||
let(:matcher) { 'TestApp::Literal#float' }
|
||||
let(:arguments) { %W(--rspec --code faa --code bbb #{matcher}) }
|
||||
|
||||
let(:filters) do
|
||||
[
|
||||
|
|
|
@ -4,6 +4,10 @@ require 'spec_helper'
|
|||
|
||||
describe Mutant::Killer::Rspec, '.new' do
|
||||
|
||||
before do
|
||||
pending 'dactivated'
|
||||
end
|
||||
|
||||
subject { object.new(strategy, mutation) }
|
||||
|
||||
let(:context) { double('Context') }
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Strategy::MethodExpansion, '.run' do
|
||||
subject { object.run(name) }
|
||||
|
||||
let(:object) { described_class }
|
||||
|
||||
context 'unexpandable and unmapped name' do
|
||||
let(:name) { :foo }
|
||||
|
||||
it { should be(:foo) }
|
||||
end
|
||||
|
||||
context 'expanded name' do
|
||||
|
||||
context 'predicate' do
|
||||
let(:name) { :foo? }
|
||||
|
||||
it { should be(:foo_predicate) }
|
||||
end
|
||||
|
||||
context 'writer' do
|
||||
let(:name) { :foo= }
|
||||
|
||||
it { should be(:foo_writer) }
|
||||
end
|
||||
|
||||
context 'bang' do
|
||||
let(:name) { :foo! }
|
||||
|
||||
it { should be(:foo_bang) }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'operator expansions' do
|
||||
|
||||
Mutant::OPERATOR_EXPANSIONS.each do |name, expansion|
|
||||
context "#{name}" do
|
||||
let(:name) { name }
|
||||
|
||||
it "should expand to #{expansion}" do
|
||||
should be(expansion)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,73 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
singleton = Mutant::Strategy::Rspec::DM2::Lookup::Method::Instance
|
||||
|
||||
describe singleton, '#spec_files' do
|
||||
subject { object.spec_files }
|
||||
|
||||
let(:object) { described_class.new(mutation_subject) }
|
||||
let(:context) { double('Context', :name => 'Foo') }
|
||||
let(:method_name) { :bar }
|
||||
let(:files) { 'Files'.freeze }
|
||||
|
||||
let(:mutation_subject) do
|
||||
double(
|
||||
'Subject',
|
||||
:name => method_name,
|
||||
:public? => is_public,
|
||||
:context => context
|
||||
)
|
||||
end
|
||||
|
||||
this_example_group = description
|
||||
|
||||
shared_examples_for this_example_group do
|
||||
it_should_behave_like 'an idempotent method'
|
||||
|
||||
before do
|
||||
if is_public
|
||||
Mutant::Strategy::MethodExpansion
|
||||
.should_receive(:run)
|
||||
.with(method_name)
|
||||
.and_return(:expanded_name)
|
||||
end
|
||||
|
||||
Dir.should_receive(:glob)
|
||||
.with(expected_glob_expression)
|
||||
.and_return(files)
|
||||
end
|
||||
|
||||
it { should be(files) }
|
||||
it { should be_frozen }
|
||||
end
|
||||
|
||||
context 'with public method' do
|
||||
let(:is_public) { true }
|
||||
let(:expected_glob_expression) { 'spec/unit/foo/expanded_name_spec.rb' }
|
||||
|
||||
it_should_behave_like this_example_group
|
||||
end
|
||||
|
||||
context 'with nonpublic method' do
|
||||
let(:is_public) { false }
|
||||
|
||||
context 'non initialize' do
|
||||
let(:expected_glob_expression) { 'spec/unit/foo/*_spec.rb' }
|
||||
|
||||
it_should_behave_like this_example_group
|
||||
end
|
||||
|
||||
context 'initialize' do
|
||||
let(:method_name) { :initialize }
|
||||
|
||||
let(:expected_glob_expression) do
|
||||
'{spec/unit/foo/*_spec.rb,spec/unit/foo/class_methods/new_spec.rb}'
|
||||
end
|
||||
|
||||
it_should_behave_like this_example_group
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,62 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
singleton = Mutant::Strategy::Rspec::DM2::Lookup::Method::Singleton
|
||||
|
||||
describe singleton, '#spec_files' do
|
||||
|
||||
subject { object.spec_files }
|
||||
|
||||
let(:object) { described_class.new(mutation_subject) }
|
||||
let(:method_name) { :bar }
|
||||
let(:files) { 'Files'.freeze }
|
||||
let(:context) { double('Context', :name => 'Foo') }
|
||||
|
||||
let(:mutation_subject) do
|
||||
double(
|
||||
'Subject',
|
||||
:name => method_name,
|
||||
:public? => is_public,
|
||||
:context => context
|
||||
)
|
||||
end
|
||||
|
||||
this_example_group = description
|
||||
|
||||
shared_examples_for this_example_group do
|
||||
it_should_behave_like 'an idempotent method'
|
||||
|
||||
before do
|
||||
if is_public
|
||||
Mutant::Strategy::MethodExpansion
|
||||
.should_receive(:run)
|
||||
.with(method_name)
|
||||
.and_return(:expanded_name)
|
||||
end
|
||||
Dir.should_receive(:glob)
|
||||
.with(expected_glob_expression)
|
||||
.and_return(files)
|
||||
end
|
||||
|
||||
it { should be(files) }
|
||||
it { should be_frozen }
|
||||
end
|
||||
|
||||
context 'with public method' do
|
||||
let(:is_public) { true }
|
||||
|
||||
let(:expected_glob_expression) do
|
||||
'spec/unit/foo/class_methods/expanded_name_spec.rb'
|
||||
end
|
||||
|
||||
it_should_behave_like this_example_group
|
||||
end
|
||||
|
||||
context 'with nonpublic method' do
|
||||
let(:is_public) { false }
|
||||
let(:expected_glob_expression) { 'spec/unit/foo/class_methods/*_spec.rb' }
|
||||
|
||||
it_should_behave_like this_example_group
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue