Improve rspec dm2 strategy

* Expand foo? to foo_predicate_spec.rb
* Expand foo! to foo_bang_spec.rb
* Execute all public method specs on mutation of private method
* Warn on stderr when no spec file was found (better than nothing to be
  iproved later)
This commit is contained in:
Markus Schirp 2012-12-07 16:55:53 +01:00
parent 31d718c46d
commit 7be00708b6
10 changed files with 236 additions and 48 deletions

1
TODO
View file

@ -8,3 +8,4 @@
* Mutate options on Regexp literals
* Support the numerous Rubinius::AST::SendWithArguments mutations.
* Fix rubinius to allow setting @vcall_style variable in Rubinius::AST::Send nodes.
* Aggregate warnings on missing spec files

View file

@ -91,6 +91,8 @@ require 'mutant/killer/static'
require 'mutant/killer/rspec'
require 'mutant/killer/forking'
require 'mutant/strategy'
require 'mutant/strategy/rspec'
require 'mutant/strategy/rspec/example_lookup'
require 'mutant/runner'
require 'mutant/cli'
require 'mutant/color'

View file

@ -52,17 +52,7 @@ module Mutant
def command_line_arguments
%W(
--fail-fast
) + Dir[filename_pattern]
end
# Return filename pattern
#
# @return [String]
#
# @api private
#
def filename_pattern
strategy.filename_pattern(mutation)
) + strategy.spec_files(mutation)
end
end
end

View file

@ -59,6 +59,18 @@ module Mutant
#
attr_reader :method_name
# Test if method is public
#
# @return [true]
# if method is public
#
# @retur [false]
# otherwise
#
# @api private
#
abstract_method :public?
private
# Initialize method filter
@ -77,7 +89,7 @@ module Mutant
# Return method
#
# @return [UnboundMethod]
# @return [UnboundMethod, Method]
#
# @api private
#

View file

@ -15,6 +15,20 @@ module Mutant
"#{scope.name}##{method_name}"
end
# Test if method is public
#
# @return [true]
# if method is public
#
# @return [false]
# otherwise
#
# @api private
#
def public?
scope.public_method_defined?(method_name)
end
private
# Check if node is matched

View file

@ -13,6 +13,22 @@ module Mutant
def identification
"#{scope.name}.#{method_name}"
end
memoize :identification
# Test if method is public
#
# @return [true]
# if method is public
#
# @return [false]
# otherwise
#
# @api private
#
def public?
scope.singleton_class.public_method_defined?(method_name)
end
private

View file

@ -14,54 +14,29 @@ module Mutant
killer.new(self, mutation)
end
# Return killer
#
# @return [Class:Killer]
#
# @api private
#
def self.killer
self::KILLER
end
# Static strategies
class Static < self
# Always fail to kill strategy
class Fail < self
KILLER = Killer::Static::Fail
end
# Always succeed to kill strategy
class Success < self
KILLER = Killer::Static::Success
end
end
class Rspec < self
KILLER = Killer::Forking.new(Killer::Rspec)
class DM2 < self
def self.filename_pattern(mutation)
subject = mutation.subject
name = subject.context.scope.name
matcher = subject.matcher
append = matcher.kind_of?(Matcher::Method::Singleton) ? '/class_methods' : ''
path = Inflector.underscore(name)
base = "spec/unit/#{path}#{append}"
"#{base}/#{matcher.method_name}_spec.rb"
end
end
class Unit < self
def self.filename_pattern(mutation)
'spec/unit/**/*_spec.rb'
end
end
class Full < self
def self.filename_pattern(mutation)
'spec/**/*_spec.rb'
end
end
end
end
end

View file

@ -0,0 +1,59 @@
module Mutant
class Strategy
# Rspec strategy base class
class Rspec < self
KILLER = Killer::Forking.new(Killer::Rspec)
# DM2-style strategy
class DM2 < self
# Return filename pattern
#
# @return [String]
#
# @api private
#
def self.spec_files(mutation)
ExampleLookup.run(mutation)
end
end
# Run all unit specs per mutation
class Unit < self
# Return file name pattern for mutation
#
# @return [Mutation]
#
# @api private
#
def self.spec_files(mutation)
Dir['spec/unit/**/*_spec.rb']
end
end
# Run all integration specs per mutation
class Unit < self
# Return file name pattern for mutation
#
# @return [Mutation]
#
# @api private
#
def self.spec_files(mutation)
Dir['spec/integration/**/*_spec.rb']
end
end
# Run all specs per mutation
class Full < self
def self.spec_files(mutation)
Dir['spec/**/*_spec.rb']
end
end
end
end
end

View file

@ -0,0 +1,119 @@
module Mutant
class Strategy
class Rspec
# Example lookup for rspec
class ExampleLookup
include Adamantium::Flat, Equalizer.new(:mutation)
# Perform example lookup
#
# @param [Mutation]
#
# @return [Enumerable<String>]
#
# @api private
#
def self.run(mutation)
new(mutation).spec_files
end
# Return mutation
#
# @return [Mutation]
#
# @api private
#
attr_reader :mutation
# Return spec files
#
# @return [Enumerable<String>]
#
# @api private
#
def spec_files
expression = glob_expression
files = Dir[expression]
$stderr.puts("Spec file(s): #{expression.inspect} not found for #{mutation.inspect}")
files
end
memoize :spec_files
private
# Return method matcher
#
# @return [Matcher]
#
# @api private
#
def matcher
mutation.subject.matcher
end
# Return spec file
#
# @return [String]
#
# @api private
#
def spec_file
matcher.method_name.to_s.
gsub(/\?\z/, 'predicate').
gsub(/!\z/, 'bang') + '_spec.rb'
end
memoize :spec_file
# Return glob expression
#
# @return [String]
#
# @api private
#
def glob_expression
if mutation.subject.matcher.public?
"#{base_path}/#{spec_file}"
else
"#{base_path}/*_spec.rb"
end
end
# Return instance of singleton path appendo
#
# @return [String]
#
# @api private
#
def scope_append
matcher.kind_of?(Matcher::Method::Singleton) ? '/class_methods' : ''
end
memoize :scope_append
# Return base path
#
# @return [String]
#
# @api private
#
def base_path
"spec/unit/#{Inflector.underscore(mutation.subject.context.scope.name)}#{scope_append}"
end
memoize :base_path
# Initalize object
#
# @param [Mutation] mutation
#
# @api private
#
def initialize(mutation)
@mutation = mutation
end
end
end
end
end

View file

@ -4,7 +4,7 @@ describe Mutant::Killer::Rspec, '.new' do
subject { object.new(strategy, mutation) }
let(:strategy) { mock('Strategy', :filename_pattern => 'foo') }
let(:strategy) { mock('Strategy', :spec_files => ['foo']) }
let(:context) { mock('Context') }
let(:mutation) { mock('Mutation') }