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:
parent
31d718c46d
commit
7be00708b6
10 changed files with 236 additions and 48 deletions
1
TODO
1
TODO
|
@ -8,3 +8,4 @@
|
||||||
* Mutate options on Regexp literals
|
* Mutate options on Regexp literals
|
||||||
* Support the numerous Rubinius::AST::SendWithArguments mutations.
|
* Support the numerous Rubinius::AST::SendWithArguments mutations.
|
||||||
* Fix rubinius to allow setting @vcall_style variable in Rubinius::AST::Send nodes.
|
* Fix rubinius to allow setting @vcall_style variable in Rubinius::AST::Send nodes.
|
||||||
|
* Aggregate warnings on missing spec files
|
||||||
|
|
|
@ -91,6 +91,8 @@ require 'mutant/killer/static'
|
||||||
require 'mutant/killer/rspec'
|
require 'mutant/killer/rspec'
|
||||||
require 'mutant/killer/forking'
|
require 'mutant/killer/forking'
|
||||||
require 'mutant/strategy'
|
require 'mutant/strategy'
|
||||||
|
require 'mutant/strategy/rspec'
|
||||||
|
require 'mutant/strategy/rspec/example_lookup'
|
||||||
require 'mutant/runner'
|
require 'mutant/runner'
|
||||||
require 'mutant/cli'
|
require 'mutant/cli'
|
||||||
require 'mutant/color'
|
require 'mutant/color'
|
||||||
|
|
|
@ -52,17 +52,7 @@ module Mutant
|
||||||
def command_line_arguments
|
def command_line_arguments
|
||||||
%W(
|
%W(
|
||||||
--fail-fast
|
--fail-fast
|
||||||
) + Dir[filename_pattern]
|
) + strategy.spec_files(mutation)
|
||||||
end
|
|
||||||
|
|
||||||
# Return filename pattern
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def filename_pattern
|
|
||||||
strategy.filename_pattern(mutation)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,6 +59,18 @@ module Mutant
|
||||||
#
|
#
|
||||||
attr_reader :method_name
|
attr_reader :method_name
|
||||||
|
|
||||||
|
# Test if method is public
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# if method is public
|
||||||
|
#
|
||||||
|
# @retur [false]
|
||||||
|
# otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
abstract_method :public?
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Initialize method filter
|
# Initialize method filter
|
||||||
|
@ -77,7 +89,7 @@ module Mutant
|
||||||
|
|
||||||
# Return method
|
# Return method
|
||||||
#
|
#
|
||||||
# @return [UnboundMethod]
|
# @return [UnboundMethod, Method]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
|
|
|
@ -15,6 +15,20 @@ module Mutant
|
||||||
"#{scope.name}##{method_name}"
|
"#{scope.name}##{method_name}"
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
# Check if node is matched
|
# Check if node is matched
|
||||||
|
|
|
@ -13,6 +13,22 @@ module Mutant
|
||||||
def identification
|
def identification
|
||||||
"#{scope.name}.#{method_name}"
|
"#{scope.name}.#{method_name}"
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
|
|
|
@ -14,54 +14,29 @@ module Mutant
|
||||||
killer.new(self, mutation)
|
killer.new(self, mutation)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return killer
|
||||||
|
#
|
||||||
|
# @return [Class:Killer]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
def self.killer
|
def self.killer
|
||||||
self::KILLER
|
self::KILLER
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Static strategies
|
||||||
class Static < self
|
class Static < self
|
||||||
|
|
||||||
|
# Always fail to kill strategy
|
||||||
class Fail < self
|
class Fail < self
|
||||||
KILLER = Killer::Static::Fail
|
KILLER = Killer::Static::Fail
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Always succeed to kill strategy
|
||||||
class Success < self
|
class Success < self
|
||||||
KILLER = Killer::Static::Success
|
KILLER = Killer::Static::Success
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
59
lib/mutant/strategy/rspec.rb
Normal file
59
lib/mutant/strategy/rspec.rb
Normal 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
|
119
lib/mutant/strategy/rspec/example_lookup.rb
Normal file
119
lib/mutant/strategy/rspec/example_lookup.rb
Normal 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
|
|
@ -4,7 +4,7 @@ describe Mutant::Killer::Rspec, '.new' do
|
||||||
|
|
||||||
subject { object.new(strategy, mutation) }
|
subject { object.new(strategy, mutation) }
|
||||||
|
|
||||||
let(:strategy) { mock('Strategy', :filename_pattern => 'foo') }
|
let(:strategy) { mock('Strategy', :spec_files => ['foo']) }
|
||||||
let(:context) { mock('Context') }
|
let(:context) { mock('Context') }
|
||||||
let(:mutation) { mock('Mutation') }
|
let(:mutation) { mock('Mutation') }
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue