Finalize method matching
* Add tests for all edge cases I could create * Add infrastructure for loading mutations into the vm. * The fun part is next!
This commit is contained in:
parent
dc893bfd7d
commit
10c3dfc390
34 changed files with 1070 additions and 108 deletions
11
.travis.yml
Normal file
11
.travis.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
before_install:
|
||||||
|
- gem install bundler
|
||||||
|
language: ruby
|
||||||
|
script: 'bundle exec rake ci'
|
||||||
|
rvm:
|
||||||
|
- 1.8.7
|
||||||
|
- rbx-18mode
|
||||||
|
- rbx-19mode
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
- mbj@seonic.net
|
||||||
2
TODO
2
TODO
|
|
@ -10,3 +10,5 @@
|
||||||
a second time with a differend module name ast node.
|
a second time with a differend module name ast node.
|
||||||
* Get a rid of rspec-1 (can be done once we do not use heckle anymore)
|
* Get a rid of rspec-1 (can be done once we do not use heckle anymore)
|
||||||
* Add an infrastructure to whitelist for components to heckle on ruby-1.8.
|
* Add an infrastructure to whitelist for components to heckle on ruby-1.8.
|
||||||
|
* Allow matches on attr_reader with literal name argument(s)?
|
||||||
|
* Allow matches on define_method with literal name argument?
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
---
|
---
|
||||||
threshold: 8
|
threshold: 11
|
||||||
total_score: 50
|
total_score: 96
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ ClassVariableCheck: {}
|
||||||
CyclomaticComplexityBlockCheck:
|
CyclomaticComplexityBlockCheck:
|
||||||
complexity: 2
|
complexity: 2
|
||||||
CyclomaticComplexityMethodCheck:
|
CyclomaticComplexityMethodCheck:
|
||||||
complexity: 3
|
complexity: 4
|
||||||
EmptyRescueBodyCheck: {}
|
EmptyRescueBodyCheck: {}
|
||||||
ForLoopCheck: {}
|
ForLoopCheck: {}
|
||||||
MethodLineCountCheck:
|
MethodLineCountCheck:
|
||||||
|
|
@ -23,4 +23,4 @@ ModuleLineCountCheck:
|
||||||
ModuleNameCheck:
|
ModuleNameCheck:
|
||||||
pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/
|
pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/
|
||||||
ParameterNumberCheck:
|
ParameterNumberCheck:
|
||||||
parameter_count: 2
|
parameter_count: 3
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ UncommunicativeParameterName:
|
||||||
- !ruby/regexp /[0-9]$/
|
- !ruby/regexp /[0-9]$/
|
||||||
- !ruby/regexp /[A-Z]/
|
- !ruby/regexp /[A-Z]/
|
||||||
LargeClass:
|
LargeClass:
|
||||||
max_methods: 11
|
max_methods: 10
|
||||||
exclude: []
|
exclude:
|
||||||
|
- "Mutant::Matcher::Method" # 13 methods
|
||||||
enabled: true
|
enabled: true
|
||||||
max_instance_variables: 2
|
max_instance_variables: 3
|
||||||
UncommunicativeMethodName:
|
UncommunicativeMethodName:
|
||||||
accept: []
|
accept: []
|
||||||
exclude: []
|
exclude: []
|
||||||
|
|
@ -22,7 +23,8 @@ UncommunicativeMethodName:
|
||||||
- !ruby/regexp /[A-Z]/
|
- !ruby/regexp /[A-Z]/
|
||||||
LongParameterList:
|
LongParameterList:
|
||||||
max_params: 2
|
max_params: 2
|
||||||
exclude: []
|
exclude:
|
||||||
|
- "Mutant::Context::Constant#initialize" # 3 params
|
||||||
enabled: true
|
enabled: true
|
||||||
overrides: {}
|
overrides: {}
|
||||||
FeatureEnvy:
|
FeatureEnvy:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# For Veritas::Immutable will be extracted soon
|
# For Veritas::Immutable. will be extracted soon
|
||||||
require 'veritas'
|
require 'veritas'
|
||||||
|
|
||||||
# Library namespace
|
# Library namespace
|
||||||
|
|
@ -13,12 +13,12 @@ module Mutant
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# class Foo
|
# class Foo
|
||||||
# def x
|
# def bar
|
||||||
# Mutant.not_implemented(self)
|
# Mutant.not_implemented(self)
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Foo.new.x # raises NotImplementedError "Foo#x is not implemented"
|
# Foo.new.x # raises NotImplementedError "Foo#bar is not implemented"
|
||||||
#
|
#
|
||||||
# @return [undefined]
|
# @return [undefined]
|
||||||
#
|
#
|
||||||
|
|
@ -49,6 +49,11 @@ module Mutant
|
||||||
private_class_method :not_implemented_info
|
private_class_method :not_implemented_info
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'mutant/mutator'
|
||||||
|
require 'mutant/loader'
|
||||||
|
require 'mutant/context'
|
||||||
|
require 'mutant/context/constant'
|
||||||
|
require 'mutant/mutatee'
|
||||||
require 'mutant/matcher'
|
require 'mutant/matcher'
|
||||||
require 'mutant/matcher/method'
|
require 'mutant/matcher/method'
|
||||||
require 'mutant/matcher/method/singleton'
|
require 'mutant/matcher/method/singleton'
|
||||||
|
|
|
||||||
18
lib/mutant/context.rb
Normal file
18
lib/mutant/context.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
module Mutant
|
||||||
|
# An abstract context where mutations can be appied to.
|
||||||
|
class Context
|
||||||
|
include Veritas::Immutable
|
||||||
|
|
||||||
|
# Return root ast for mutated node
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @return [Rubinis::AST::Script]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def root(node)
|
||||||
|
Mutant.not_implemented(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
106
lib/mutant/context/constant.rb
Normal file
106
lib/mutant/context/constant.rb
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
module Mutant
|
||||||
|
class Context
|
||||||
|
# Constant context for mutation (Class or Module)
|
||||||
|
class Constant < Context
|
||||||
|
include Veritas::Immutable
|
||||||
|
|
||||||
|
private_class_method :new
|
||||||
|
|
||||||
|
TABLE = {
|
||||||
|
::Class => ['class', Rubinius::AST::ClassScope],
|
||||||
|
::Module => ['module', Rubinius::AST::ModuleScope]
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# Build constant from class or module
|
||||||
|
#
|
||||||
|
# @param [::Class|::Module] value
|
||||||
|
#
|
||||||
|
# @return [Constant]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.build(value)
|
||||||
|
arguments = TABLE.fetch(value.class) do
|
||||||
|
raise ArgumentError, 'Can only build constant mutation scope from class or module'
|
||||||
|
end
|
||||||
|
|
||||||
|
new(value,*arguments)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return ast wrapping mutated node
|
||||||
|
#
|
||||||
|
# @return [Rubinius::AST::Script]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def root(node)
|
||||||
|
root = root_ast
|
||||||
|
root.body = [@scope_class.new(1,root.name,[node])]
|
||||||
|
Rubinius::AST::Script.new(root)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return unqualified name of constant
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def unqualified_name
|
||||||
|
name_nesting.last
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return constant wrapped by context
|
||||||
|
#
|
||||||
|
# @return [::Module|::Class]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
attr_reader :constant
|
||||||
|
private :constant
|
||||||
|
|
||||||
|
# Initialize object
|
||||||
|
#
|
||||||
|
# @param [Object] constant
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def initialize(constant,keyword,scope_class)
|
||||||
|
@constant,@keyword,@scope_class = constant,keyword,scope_class
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return new root ast
|
||||||
|
#
|
||||||
|
# @return [Rubinius::AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def root_ast
|
||||||
|
"#{@keyword} #{qualified_name}; end".to_ast
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return qualified name of constant
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def qualified_name
|
||||||
|
@constant.name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return nesting of names of constant
|
||||||
|
#
|
||||||
|
# @return [Array<String>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def name_nesting
|
||||||
|
@constant.name.split('::')
|
||||||
|
end
|
||||||
|
|
||||||
|
memoize :unqualified_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
82
lib/mutant/loader.rb
Normal file
82
lib/mutant/loader.rb
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
module Mutant
|
||||||
|
# A method object for inserting an AST into the Rubinius VM
|
||||||
|
#
|
||||||
|
# The idea is to split the steps for a mutation into documented
|
||||||
|
# methods. Also subclasses can override the steps. Also passing
|
||||||
|
# around the root node is not needed with a method object.
|
||||||
|
#
|
||||||
|
# As the initializer does the work there is no need for the
|
||||||
|
# instances of this class to be used outside of this class, hence
|
||||||
|
# the Loader.new method is private and the Loader.run method
|
||||||
|
# returns self.
|
||||||
|
#
|
||||||
|
class Loader
|
||||||
|
private_class_method :new
|
||||||
|
|
||||||
|
# Load an AST into the rubinius VM
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Script] root
|
||||||
|
# A root AST node to be loaded into the VM
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.load(root)
|
||||||
|
new(root)
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Initialize and insert mutation into vm
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Script] root
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def initialize(root)
|
||||||
|
@root = root
|
||||||
|
Rubinius.run_script(script)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return compiled method script for node
|
||||||
|
#
|
||||||
|
# @return [Rubinius::CompiledMethod::Script]A
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def script
|
||||||
|
compiled_code.create_script
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return compiled code for node
|
||||||
|
#
|
||||||
|
# This method actually returns a Rubnius::CompiledMethod
|
||||||
|
# instance. But it is named on the future name of CompiledMethod
|
||||||
|
# that will be renamed to Rubinius::CompiledCode.
|
||||||
|
#
|
||||||
|
# @return [Rubinius::CompiledMethod]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def compiled_code
|
||||||
|
compiler.run
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return compiler loaded with mutated ast
|
||||||
|
#
|
||||||
|
# @return [Rubinius::Compiler]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def compiler
|
||||||
|
Rubinius::Compiler.new(:bytecode, :compiled_method).tap do |compiler|
|
||||||
|
compiler.generator.input(@root)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
module Mutant
|
module Mutant
|
||||||
# Abstract filter for rubinius asts.
|
# Abstract matcher to find ASTs to mutate
|
||||||
class Matcher
|
class Matcher
|
||||||
include Enumerable
|
include Enumerable
|
||||||
|
|
||||||
# Return each matched node
|
# Enumerate mutatees
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
|
|
@ -12,5 +12,8 @@ module Mutant
|
||||||
def each
|
def each
|
||||||
Mutant.not_implemented(self)
|
Mutant.not_implemented(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
module Mutant
|
module Mutant
|
||||||
class Matcher
|
class Matcher
|
||||||
# A filter for methods
|
# Matcher to find AST for method
|
||||||
class Method < Matcher
|
class Method < Matcher
|
||||||
|
include Veritas::Immutable
|
||||||
|
|
||||||
# Parse a method string into filter
|
# Parse a method string into filter
|
||||||
#
|
#
|
||||||
# @param [String] input
|
# @param [String] input
|
||||||
|
|
@ -26,21 +28,25 @@ module Mutant
|
||||||
#
|
#
|
||||||
def each(&block)
|
def each(&block)
|
||||||
return to_enum(__method__) unless block_given?
|
return to_enum(__method__) unless block_given?
|
||||||
node = root_node
|
mutatee.tap do |mutatee|
|
||||||
yield node if node
|
yield mutatee if mutatee
|
||||||
|
end
|
||||||
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
# Return context of matcher
|
||||||
|
|
||||||
# Return constant name
|
|
||||||
#
|
#
|
||||||
# @return [String]
|
# @return [Context]
|
||||||
|
# returns the context this matcher matches AST nodes
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
attr_reader :constant_name
|
def context
|
||||||
private :constant_name
|
Context::Constant.build(constant)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
# Return method name
|
# Return method name
|
||||||
#
|
#
|
||||||
|
|
@ -51,6 +57,16 @@ module Mutant
|
||||||
attr_reader :method_name
|
attr_reader :method_name
|
||||||
private :method_name
|
private :method_name
|
||||||
|
|
||||||
|
# Return constant name
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
attr_reader :constant_name
|
||||||
|
private :constant_name
|
||||||
|
|
||||||
|
|
||||||
# Initialize method filter
|
# Initialize method filter
|
||||||
#
|
#
|
||||||
# @param [String] constant_name
|
# @param [String] constant_name
|
||||||
|
|
@ -61,7 +77,7 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def initialize(constant_name, method_name)
|
def initialize(constant_name, method_name)
|
||||||
@constant_name, @method_name = constant_name, method_name
|
@constant_name,@method_name = constant_name, method_name
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return method
|
# Return method
|
||||||
|
|
@ -142,19 +158,27 @@ module Mutant
|
||||||
method.source_location
|
method.source_location
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return root node
|
# Return matched node
|
||||||
#
|
#
|
||||||
# @return [Rubinus::AST]
|
# @return [Rubinis::AST::Node]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def root_node
|
def matched_node
|
||||||
root_node = nil
|
Mutant.not_implemented(self)
|
||||||
ast.walk do |predicate, node|
|
end
|
||||||
root_node = node if match?(node)
|
|
||||||
true
|
# Return mutatee
|
||||||
|
#
|
||||||
|
# @return [Mutatee]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def mutatee
|
||||||
|
node = matched_node
|
||||||
|
if node
|
||||||
|
Mutatee.new(context,node)
|
||||||
end
|
end
|
||||||
root_node
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return constant
|
# Return constant
|
||||||
|
|
@ -164,10 +188,12 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def constant
|
def constant
|
||||||
constant_name.split(/::/).inject(Object) do |context, name|
|
constant_name.split('::').inject(::Object) do |parent,name|
|
||||||
context.const_get(name)
|
parent.const_get(name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
memoize :mutatee
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
module Mutant
|
module Mutant
|
||||||
class Matcher
|
class Matcher
|
||||||
class Method < Matcher
|
class Method < Matcher
|
||||||
# A instance method filter
|
# Matcher for instance methods
|
||||||
class Instance < Method
|
class Instance < Method
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Return method instance
|
# Return method instance
|
||||||
#
|
#
|
||||||
# @return [UnboundMethod]
|
# @return [UnboundMethod]
|
||||||
|
|
@ -23,6 +25,21 @@ module Mutant
|
||||||
def node_class
|
def node_class
|
||||||
Rubinius::AST::Define
|
Rubinius::AST::Define
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return matched node
|
||||||
|
#
|
||||||
|
# @return [Rubinus::AST::Define]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def matched_node
|
||||||
|
last_match = nil
|
||||||
|
ast.walk do |predicate, node|
|
||||||
|
last_match = node if match?(node)
|
||||||
|
predicate
|
||||||
|
end
|
||||||
|
last_match
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
module Mutant
|
module Mutant
|
||||||
class Matcher
|
class Matcher
|
||||||
class Method
|
class Method
|
||||||
# A singleton method filter
|
# Matcher for singleton methods
|
||||||
class Singleton < Method
|
class Singleton < Method
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Return method instance
|
# Return method instance
|
||||||
#
|
#
|
||||||
# @return [UnboundMethod]
|
# @return [UnboundMethod]
|
||||||
|
|
@ -16,13 +18,90 @@ module Mutant
|
||||||
|
|
||||||
# Return matched node class
|
# Return matched node class
|
||||||
#
|
#
|
||||||
# @return [Rubinius::AST::Define]
|
# @return [Rubinius::AST::DefineSingletonScope]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def node_class
|
def node_class
|
||||||
Rubinius::AST::DefineSingletonScope
|
Rubinius::AST::DefineSingletonScope
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check for stopping AST walk on branch
|
||||||
|
#
|
||||||
|
# This method exist to protect against the
|
||||||
|
# artifical edge case where DefineSingleton nodes
|
||||||
|
# with differend receivers exist on the same line.
|
||||||
|
#
|
||||||
|
# @param [Rubnius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# returns true when node should NOT be followed
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# returns false when node can be followed
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def stop?(node)
|
||||||
|
node.is_a?(Rubinius::AST::DefineSingleton) && !match_receiver?(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if receiver matches
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::DefineSingleton] node
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# returns true when receiver is self or constant from pattern
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# returns false otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def match_receiver?(node)
|
||||||
|
receiver = node.receiver
|
||||||
|
case receiver
|
||||||
|
when Rubinius::AST::Self
|
||||||
|
true
|
||||||
|
when Rubinius::AST::ConstantAccess
|
||||||
|
match_receiver_name?(receiver)
|
||||||
|
else
|
||||||
|
raise 'Can only match receiver on Rubinius::AST::Self or Rubinius::AST::ConstantAccess'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if reciver name matches context
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
# returns true when node name matches unqualified constant name
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
# returns false otherwise
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def match_receiver_name?(node)
|
||||||
|
node.name.to_s == context.unqualified_name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return matched node
|
||||||
|
#
|
||||||
|
# @return [Rubinus::AST::DefineSingletonScope]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def matched_node
|
||||||
|
last_match = nil
|
||||||
|
ast.walk do |predicate, node|
|
||||||
|
if match?(node)
|
||||||
|
last_match = node
|
||||||
|
end
|
||||||
|
!stop?(node)
|
||||||
|
end
|
||||||
|
last_match
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
85
lib/mutant/mutatee.rb
Normal file
85
lib/mutant/mutatee.rb
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
module Mutant
|
||||||
|
# Represent a mutatable AST and its context
|
||||||
|
class Mutatee
|
||||||
|
include Veritas::Immutable, Enumerable
|
||||||
|
|
||||||
|
# Return context
|
||||||
|
#
|
||||||
|
# @return [Context]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
attr_reader :context
|
||||||
|
|
||||||
|
# Return AST node
|
||||||
|
#
|
||||||
|
# @return [Rubinius::AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
attr_reader :node
|
||||||
|
|
||||||
|
# Enumerate possible mutations
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
# returns self if block given
|
||||||
|
#
|
||||||
|
# @return [Enumerator]
|
||||||
|
# returns eumerator if no block given
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def each(&block)
|
||||||
|
return to_enum(__method__) unless block_given?
|
||||||
|
Mutator.new(node).each(&block)
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reset implementation to original
|
||||||
|
#
|
||||||
|
# This method inserts the original node again.
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def reset
|
||||||
|
insert(@node)
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Initialize a mutatee
|
||||||
|
#
|
||||||
|
# @param [Context] context
|
||||||
|
# the context of mutations
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
# the node to be mutated
|
||||||
|
#
|
||||||
|
# @return [unkown]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def initialize(context,node)
|
||||||
|
@context,@node = context,node
|
||||||
|
end
|
||||||
|
|
||||||
|
# Insert AST node under context
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def insert(node)
|
||||||
|
Loader.load(context.root(node))
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
23
lib/mutant/mutator.rb
Normal file
23
lib/mutant/mutator.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
module Mutant
|
||||||
|
# Mutate rubinius AST nodes
|
||||||
|
class Mutator
|
||||||
|
# Initialize mutator
|
||||||
|
#
|
||||||
|
# @param [Rubinius::AST::Node] node
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def initialize(node)
|
||||||
|
@node = node
|
||||||
|
end
|
||||||
|
|
||||||
|
# # Enumerate mutated asts
|
||||||
|
# #
|
||||||
|
# # @api private
|
||||||
|
# #
|
||||||
|
# def each
|
||||||
|
# return to_enum(__method__) unless block_given?
|
||||||
|
# self
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,82 +1,254 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
if defined?(Rubinius)
|
|
||||||
class Toplevel
|
|
||||||
def simple
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.simple
|
if "".respond_to?(:to_ast)
|
||||||
end
|
describe Mutant,'method matching' do
|
||||||
|
after do
|
||||||
def multiple; end;
|
if defined?(::Foo)
|
||||||
def multiple(foo); end
|
Object.send(:remove_const,'Foo')
|
||||||
|
|
||||||
def self.complex; end; def complex(foo); end
|
|
||||||
|
|
||||||
class Nested
|
|
||||||
def foo
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :foo
|
before do
|
||||||
|
eval(body)
|
||||||
def multiline(
|
File.stub(:read => body)
|
||||||
foo
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
let(:defaults) { {} }
|
||||||
|
|
||||||
describe Mutant,'method matching' do
|
context 'on instance methods' do
|
||||||
def match(input)
|
let(:pattern) { 'Foo#bar' }
|
||||||
Mutant::Matcher::Method.parse(input).to_a.first
|
let(:defaults) do
|
||||||
|
{
|
||||||
|
:constant => Foo,
|
||||||
|
:node_class => Rubinius::AST::Define,
|
||||||
|
:method_name => :bar,
|
||||||
|
:method_arity => 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when method is defined once' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
class Foo
|
||||||
|
def bar; end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{ :method_line => 2 }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when method is defined multiple times' do
|
||||||
|
context 'on differend lines' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
class Foo
|
||||||
|
def bar; end
|
||||||
|
def bar(arg); end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:method_line => 3,
|
||||||
|
:method_arity => 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on the same line' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
class Foo
|
||||||
|
def bar; end; def bar(arg); end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:method_line => 2,
|
||||||
|
:method_arity => 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'on the same line with differend scope' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
class Foo
|
||||||
|
def self.bar; end; def bar(arg); end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:method_line => 2,
|
||||||
|
:method_arity => 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when nested' do
|
||||||
|
let(:pattern) { 'Foo::Bar#baz' }
|
||||||
|
|
||||||
|
context 'in class' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
class Foo
|
||||||
|
class Bar
|
||||||
|
def baz; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:method_line => 3,
|
||||||
|
:method_name => :baz,
|
||||||
|
:constant => Foo::Bar
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in module' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
module Foo
|
||||||
|
class Bar
|
||||||
|
def baz; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:method_line => 3,
|
||||||
|
:method_name => :baz,
|
||||||
|
:constant => Foo::Bar
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows to match simple instance methods' do
|
context 'on singleton methods' do
|
||||||
match = match('Toplevel#simple')
|
let(:pattern) { 'Foo.bar' }
|
||||||
match.name.should be(:simple)
|
let(:defaults) do
|
||||||
match.line.should be(4)
|
{
|
||||||
match.arguments.required.should be_empty
|
:constant => Foo,
|
||||||
end
|
:node_class => Rubinius::AST::DefineSingletonScope,
|
||||||
|
:method_arity => 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
it 'allows to match simple singleton methods' do
|
context 'when defined on self' do
|
||||||
match = match('Toplevel.simple')
|
let(:body) do
|
||||||
match.name.should be(:simple)
|
<<-RUBY
|
||||||
match.line.should be(7)
|
class Foo
|
||||||
match.arguments.required.should be_empty
|
def self.bar; end
|
||||||
end
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns last method definition' do
|
|
||||||
match = match('Toplevel#multiple')
|
|
||||||
match.name.should be(:multiple)
|
|
||||||
match.line.should be(11)
|
|
||||||
match.arguments.required.length.should be(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not fail on multiple definitions of differend scope per row' do
|
let(:expectation) do
|
||||||
match = match('Toplevel.complex')
|
{
|
||||||
match.name.should be(:complex)
|
:method_name => :bar,
|
||||||
match.line.should be(13)
|
:method_line => 2,
|
||||||
match.arguments.required.length.should be(0)
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows matching on nested methods' do
|
it_should_behave_like 'a method match'
|
||||||
match = match('Toplevel::Nested#foo')
|
end
|
||||||
match.name.should be(:foo)
|
|
||||||
match.line.should be(16)
|
|
||||||
match.arguments.required.length.should be(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
# pending 'allows matching on attr_readers' do
|
context 'when defined on constant' do
|
||||||
# match = match('Toplevel#foo')
|
|
||||||
# match.name.should be(:foo)
|
|
||||||
# match.line.should be(19)
|
|
||||||
# match.arguments.required.length.should be(0)
|
|
||||||
# end
|
|
||||||
|
|
||||||
it 'does not fail on multi line defs' do
|
context 'inside namespace' do
|
||||||
match = match('Toplevel#multiline')
|
let(:body) do
|
||||||
match.name.should be(:multiline)
|
<<-RUBY
|
||||||
match.line.should be(23)
|
class Foo
|
||||||
match.arguments.required.length.should be(1)
|
def Foo.bar; end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:method_name => :bar,
|
||||||
|
:method_line => 2,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'outside namespace' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
class Foo; end;
|
||||||
|
def Foo.bar; end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:method_name => :bar,
|
||||||
|
:method_line => 2,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when defined multiple times in the same line' do
|
||||||
|
context 'with method on differend scope' do
|
||||||
|
let(:body) do
|
||||||
|
<<-RUBY
|
||||||
|
module Foo; end
|
||||||
|
|
||||||
|
module Bar
|
||||||
|
def self.baz; end; def Foo.baz(arg); end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:pattern) { 'Bar.baz' }
|
||||||
|
|
||||||
|
let(:expectation) do
|
||||||
|
{
|
||||||
|
:constant => Bar,
|
||||||
|
:method_name => :baz,
|
||||||
|
:method_line => 4,
|
||||||
|
:method_arity => 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'a method match'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
39
spec/shared/method_match_behavior.rb
Normal file
39
spec/shared/method_match_behavior.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
shared_examples_for 'a method match' do
|
||||||
|
subject { Mutant::Matcher::Method.parse(pattern).to_a }
|
||||||
|
|
||||||
|
let(:values) { defaults.merge(expectation) }
|
||||||
|
|
||||||
|
let(:method_name) { values.fetch(:method_name) }
|
||||||
|
let(:method_line) { values.fetch(:method_line) }
|
||||||
|
let(:method_arity) { values.fetch(:method_arity) }
|
||||||
|
let(:constant) { values.fetch(:constant) }
|
||||||
|
let(:node_class) { values.fetch(:node_class) }
|
||||||
|
|
||||||
|
let(:node) { mutatee.node }
|
||||||
|
let(:context) { mutatee.context }
|
||||||
|
let(:mutatee) { subject.first }
|
||||||
|
|
||||||
|
it 'should return one mutatee' do
|
||||||
|
subject.size.should be(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have correct method name' do
|
||||||
|
node.name.should eql(method_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have correct line number' do
|
||||||
|
node.line.should eql(method_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have correct arity' do
|
||||||
|
node.arguments.required.length.should eql(method_arity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have correct constant in context' do
|
||||||
|
context.send(:constant).should eql(constant)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have the correct node class' do
|
||||||
|
node.should be_a(node_class)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
require 'mutant'
|
|
||||||
require 'spec'
|
require 'spec'
|
||||||
require 'spec/autorun'
|
require 'spec/autorun'
|
||||||
|
|
||||||
# require spec support files and shared behavior
|
# 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 { |f| require f }
|
||||||
|
|
||||||
|
require 'mutant'
|
||||||
|
|
||||||
Spec::Runner.configure do |config|
|
Spec::Runner.configure do |config|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
48
spec/support/fake_ast.rb
Normal file
48
spec/support/fake_ast.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
unless defined?(Rubinius)
|
||||||
|
module Rubinius
|
||||||
|
# Dummy AST namespace
|
||||||
|
module AST
|
||||||
|
# Dummy node
|
||||||
|
class Node
|
||||||
|
attr_reader :line, :name
|
||||||
|
|
||||||
|
attr_accessor :body
|
||||||
|
|
||||||
|
def initialize(line,name,body=[])
|
||||||
|
@line,@name,@body = line,name,body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ConstantScope < Node
|
||||||
|
end
|
||||||
|
|
||||||
|
class ClassScope < ConstantScope
|
||||||
|
end
|
||||||
|
|
||||||
|
class ModuleScope < ConstantScope
|
||||||
|
end
|
||||||
|
|
||||||
|
class Script < Node
|
||||||
|
end
|
||||||
|
|
||||||
|
# Dummy class node
|
||||||
|
class Class < Node
|
||||||
|
def initialize(line,name,superclass,body)
|
||||||
|
super(line,name)
|
||||||
|
@superclass,@body = superclass,body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Dummy module node
|
||||||
|
class Module < Node
|
||||||
|
def initialize(line,name,body)
|
||||||
|
super(line,name)
|
||||||
|
@body = body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ConstantAccess < Node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
6
spec/support/sample_subjects.rb
Normal file
6
spec/support/sample_subjects.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
module SampleSubjects
|
||||||
|
module ExampleModule
|
||||||
|
def foo; end
|
||||||
|
def self.bar; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Context::Constant,'.build' do
|
||||||
|
subject { described_class.build(constant) }
|
||||||
|
|
||||||
|
let(:object) { described_class }
|
||||||
|
let(:context) { mock('Context') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.stub(:new => context)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when constant is a module' do
|
||||||
|
let(:constant) { Module.new }
|
||||||
|
|
||||||
|
it 'should initialize context correctly' do
|
||||||
|
described_class.should_receive(:new).with(constant,'module',Rubinius::AST::ModuleScope).and_return(context)
|
||||||
|
should be(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should be(context) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when constant is a class' do
|
||||||
|
let(:constant) { Class.new }
|
||||||
|
|
||||||
|
it 'should initialize context correctly' do
|
||||||
|
described_class.should_receive(:new).with(constant,'class',Rubinius::AST::ClassScope).and_return(context)
|
||||||
|
should be(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should be(context) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when constant is not a class nor a module' do
|
||||||
|
let(:constant) { Object.new }
|
||||||
|
|
||||||
|
it 'should raise error' do
|
||||||
|
expect { subject }.to raise_error(ArgumentError,'Can only build constant mutation scope from class or module')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
24
spec/unit/mutant/context/constant/root_spec.rb
Normal file
24
spec/unit/mutant/context/constant/root_spec.rb
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
if "".respond_to?(:to_ast)
|
||||||
|
describe Mutant::Context::Constant, '#root' do
|
||||||
|
subject { object.root(node) }
|
||||||
|
|
||||||
|
let(:object) { described_class.build(SampleSubjects::ExampleModule) }
|
||||||
|
let(:node) { mock('Node') }
|
||||||
|
|
||||||
|
let(:constant) { subject.body }
|
||||||
|
let(:scope) { constant.body.first }
|
||||||
|
let(:scope_body) { scope.body }
|
||||||
|
|
||||||
|
it { should be_a(Rubinius::AST::Script) }
|
||||||
|
|
||||||
|
it 'should wrap the ast under constant' do
|
||||||
|
scope.should be_kind_of(Rubinius::AST::ModuleScope)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should place the ast under scope body' do
|
||||||
|
scope_body.should == [node]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
13
spec/unit/mutant/context/constant/unqualified_name_spec.rb
Normal file
13
spec/unit/mutant/context/constant/unqualified_name_spec.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Context::Constant, '#unqualified_name' do
|
||||||
|
subject { object.unqualified_name }
|
||||||
|
|
||||||
|
let(:object) { described_class.build(SampleSubjects::ExampleModule) }
|
||||||
|
|
||||||
|
it 'should return wrapped constants unqualified name' do
|
||||||
|
should eql('ExampleModule')
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'an idempotent method'
|
||||||
|
end
|
||||||
11
spec/unit/mutant/context/root_spec.rb
Normal file
11
spec/unit/mutant/context/root_spec.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Context,'#root' do
|
||||||
|
subject { object.root(mock) }
|
||||||
|
|
||||||
|
let(:object) { described_class.allocate }
|
||||||
|
|
||||||
|
it 'should raise error' do
|
||||||
|
expect { subject }.to raise_error('Mutant::Context#root is not implemented')
|
||||||
|
end
|
||||||
|
end
|
||||||
7
spec/unit/mutant/loader/class_methods/load_spec.rb
Normal file
7
spec/unit/mutant/loader/class_methods/load_spec.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Loader,'.load' do
|
||||||
|
subject { object.load(node) }
|
||||||
|
|
||||||
|
let(:object) { described_class }
|
||||||
|
end
|
||||||
18
spec/unit/mutant/matcher/context_spec.rb
Normal file
18
spec/unit/mutant/matcher/context_spec.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Matcher,'#context' do
|
||||||
|
subject { object.context }
|
||||||
|
|
||||||
|
let(:object) { described_class.new(constant_name) }
|
||||||
|
let(:constant_name) { 'Foo' }
|
||||||
|
let(:context) { mock('Context') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Mutant::Context.stub(:build => context)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#it { should be(context) }
|
||||||
|
|
||||||
|
#it_should_behave_like 'an idempotent method'
|
||||||
|
end
|
||||||
21
spec/unit/mutant/matcher/method/context_spec.rb
Normal file
21
spec/unit/mutant/matcher/method/context_spec.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Matcher::Method,'#context' do
|
||||||
|
subject { object.context }
|
||||||
|
|
||||||
|
let(:object) { described_class::Singleton.new('SampleSubjects::ExampleModule','foo') }
|
||||||
|
let(:context) { mock('Context') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Mutant::Context::Constant.stub(:build => context)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should be(context); }
|
||||||
|
|
||||||
|
it 'should build context with subject' do
|
||||||
|
Mutant::Context::Constant.should_receive(:build).with(::SampleSubjects::ExampleModule).and_return(context)
|
||||||
|
should be(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'an idempotent method'
|
||||||
|
end
|
||||||
|
|
@ -4,15 +4,18 @@ require 'spec_helper'
|
||||||
|
|
||||||
describe Mutant::Matcher::Method,'#each' do
|
describe Mutant::Matcher::Method,'#each' do
|
||||||
let(:class_under_test) do
|
let(:class_under_test) do
|
||||||
node = self.root_node
|
node = self.matched_node
|
||||||
Class.new(described_class) do
|
Class.new(described_class) do
|
||||||
define_method(:root_node) do
|
define_method(:matched_node) do
|
||||||
node
|
node
|
||||||
end
|
end
|
||||||
|
|
||||||
|
define_method(:constant) do
|
||||||
|
::SampleSubjects::ExampleModule
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
subject { object.each { |item| yields << item } }
|
subject { object.each { |item| yields << item } }
|
||||||
|
|
||||||
let(:object) { class_under_test.allocate }
|
let(:object) { class_under_test.allocate }
|
||||||
|
|
@ -20,16 +23,16 @@ describe Mutant::Matcher::Method,'#each' do
|
||||||
|
|
||||||
it_should_behave_like 'an #each method'
|
it_should_behave_like 'an #each method'
|
||||||
|
|
||||||
let(:root_node) { mock('Root Node') }
|
let(:matched_node) { mock('Root Node') }
|
||||||
|
|
||||||
context 'with match' do
|
context 'with match' do
|
||||||
it 'should yield root node' do
|
it 'should yield mutatee' do
|
||||||
expect { subject }.to change { yields.dup }.from([]).to([root_node])
|
expect { subject }.to change { yields.dup }.from([]).to([object.send(:mutatee)])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'without match' do
|
context 'without match' do
|
||||||
let(:root_node) { nil }
|
let(:matched_node) { nil }
|
||||||
|
|
||||||
it 'should yield nothing' do
|
it 'should yield nothing' do
|
||||||
subject
|
subject
|
||||||
|
|
|
||||||
12
spec/unit/mutant/mutatee/class_methods/new_spec.rb
Normal file
12
spec/unit/mutant/mutatee/class_methods/new_spec.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Mutatee,'.new' do
|
||||||
|
subject { object.new(context,ast) }
|
||||||
|
|
||||||
|
let(:object) { described_class }
|
||||||
|
|
||||||
|
let(:context) { mock('Context') }
|
||||||
|
let(:ast) { mock('AST') }
|
||||||
|
|
||||||
|
it { should be_frozen }
|
||||||
|
end
|
||||||
13
spec/unit/mutant/mutatee/context_spec.rb
Normal file
13
spec/unit/mutant/mutatee/context_spec.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Mutatee,'#context' do
|
||||||
|
subject { object.context }
|
||||||
|
|
||||||
|
let(:object) { described_class.new(context,ast) }
|
||||||
|
let(:ast) { mock('AST') }
|
||||||
|
let(:context) { mock('Context') }
|
||||||
|
|
||||||
|
it { should be(context) }
|
||||||
|
|
||||||
|
it_should_behave_like 'an idempotent method'
|
||||||
|
end
|
||||||
27
spec/unit/mutant/mutatee/each_spec.rb
Normal file
27
spec/unit/mutant/mutatee/each_spec.rb
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Mutatee,'#each' do
|
||||||
|
subject { object.each { |item| yields << item } }
|
||||||
|
|
||||||
|
let(:object) { described_class.new(context,ast) }
|
||||||
|
let(:root) { mock('Root AST') }
|
||||||
|
let(:ast) { mock('AST') }
|
||||||
|
let(:context) { mock('Context', :root => root) }
|
||||||
|
let(:mutation) { mock('Mutation') }
|
||||||
|
let(:mutator) { [mutation] }
|
||||||
|
let(:yields) { [] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Mutant::Mutator.stub(:new => mutator)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_should_behave_like 'an #each method'
|
||||||
|
|
||||||
|
#it 'should initialize mutator with ast' do
|
||||||
|
# Mutant::Mutator.should_receive(:new).with(ast).and_return(mutator)
|
||||||
|
#end
|
||||||
|
|
||||||
|
it 'should yield mutations' do
|
||||||
|
expect { subject }.to change { yields.dup }.from([]).to([mutation])
|
||||||
|
end
|
||||||
|
end
|
||||||
12
spec/unit/mutant/mutatee/node_spec.rb
Normal file
12
spec/unit/mutant/mutatee/node_spec.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Mutatee,'#node' do
|
||||||
|
subject { object.node }
|
||||||
|
let(:object) { described_class.new(context,node) }
|
||||||
|
let(:node) { mock('Node') }
|
||||||
|
let(:context) { mock('Context') }
|
||||||
|
|
||||||
|
it { should be(node) }
|
||||||
|
|
||||||
|
it_should_behave_like 'an idempotent method'
|
||||||
|
end
|
||||||
26
spec/unit/mutant/mutatee/reset_spec.rb
Normal file
26
spec/unit/mutant/mutatee/reset_spec.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Mutant::Mutatee,'#reset' do
|
||||||
|
subject { object.reset }
|
||||||
|
|
||||||
|
let(:object) { described_class.new(context,ast) }
|
||||||
|
let(:root) { mock('Root AST') }
|
||||||
|
let(:ast) { mock('AST') }
|
||||||
|
let(:context) { mock('Context', :root => root) }
|
||||||
|
|
||||||
|
it_should_behave_like 'a command method'
|
||||||
|
|
||||||
|
before do
|
||||||
|
Mutant::Loader.stub(:load => Mutant::Loader)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should create root ast from context' do
|
||||||
|
context.should_receive(:root).with(ast).and_return(root)
|
||||||
|
should be(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should insert root ast' do
|
||||||
|
Mutant::Loader.should_receive(:load).with(root).and_return(Mutant::Loader)
|
||||||
|
should be(object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -34,6 +34,7 @@ begin
|
||||||
raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle"
|
raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require File.expand_path('../../../spec/support/fake_ast',__FILE__)
|
||||||
require 'mutant'
|
require 'mutant'
|
||||||
|
|
||||||
root_module_regexp = Regexp.union('Mutant')
|
root_module_regexp = Regexp.union('Mutant')
|
||||||
|
|
@ -54,6 +55,13 @@ begin
|
||||||
ObjectSpace.each_object(Module) do |mod|
|
ObjectSpace.each_object(Module) do |mod|
|
||||||
next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/
|
next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/
|
||||||
|
|
||||||
|
# Mutation::Loader is to rbx specific
|
||||||
|
next if mod == Mutant::Loader
|
||||||
|
# Mutation::Matcher::Method is to rbx specific
|
||||||
|
next if mod == Mutant::Matcher::Method
|
||||||
|
# Mutation::Context::Constant is to rbx specific
|
||||||
|
next if mod == Mutant::Context::Constant
|
||||||
|
|
||||||
spec_prefix = spec_dir.join(mod.name.underscore)
|
spec_prefix = spec_dir.join(mod.name.underscore)
|
||||||
|
|
||||||
specs = []
|
specs = []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue