Add mutations on singleton methods

* Fixing singleton matcher to return Rubiniuy::AST::DefineSingleton
  and not Rubinius::AST::DefineSingletonScope
* Adjust counts of runner spec
This commit is contained in:
Markus Schirp 2012-08-16 19:10:24 +02:00
parent 23199b3c94
commit 583668138e
9 changed files with 91 additions and 81 deletions

View file

@ -86,34 +86,6 @@ module Mutant
# #
abstract_method :method abstract_method :method
# Return node classes this matcher matches
#
# @return [Rubinius::AST::Node]
#
# @api private
#
def node_class
self.class::NODE_CLASS
end
# Check if node is matched
#
# @param [Rubinius::AST::Node] node
#
# @return [true]
# returns true if node matches method
#
# @return [false]
# returns false if node NOT matches method
#
# @api private
#
def match?(node)
node.line == source_line &&
node.class == node_class &&
node.name == method_name
end
# Return full ast # Return full ast
# #
# @return [Rubinius::AST::Node] # @return [Rubinius::AST::Node]
@ -178,6 +150,21 @@ module Mutant
Subject.new(context, node) Subject.new(context, node)
end end
memoize :subject memoize :subject
# Return matched node
#
# @return [Rubinus::AST::Node]
#
# @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

View file

@ -4,8 +4,6 @@ module Mutant
# Matcher for instance methods # Matcher for instance methods
class Instance < self class Instance < self
NODE_CLASS = Rubinius::AST::Define
# Extract instance method matchers from scope # Extract instance method matchers from scope
# #
# @param [Class|Module] scope # @param [Class|Module] scope
@ -31,8 +29,27 @@ module Mutant
def identification def identification
"#{scope.name}##{method_name}" "#{scope.name}##{method_name}"
end end
private private
# Check if node is matched
#
# @param [Rubinius::AST::Node] node
#
# @return [true]
# returns true if node matches method
#
# @return [false]
# returns false if node NOT matches method
#
# @api private
#
def match?(node)
node.line == source_line &&
node.class == Rubinius::AST::Define &&
node.name == method_name
end
# Return method instance # Return method instance
# #
# @return [UnboundMethod] # @return [UnboundMethod]
@ -43,20 +60,6 @@ module Mutant
scope.instance_method(method_name) scope.instance_method(method_name)
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

View file

@ -4,8 +4,6 @@ module Mutant
# Matcher for singleton methods # Matcher for singleton methods
class Singleton < self class Singleton < self
NODE_CLASS = Rubinius::AST::DefineSingletonScope
# Return matcher enumerable # Return matcher enumerable
# #
# @param [Class|Module] scope # @param [Class|Module] scope
@ -16,11 +14,10 @@ module Mutant
# #
def self.each(scope) def self.each(scope)
return to_enum unless block_given? return to_enum unless block_given?
return unless scope.kind_of?(Module)
scope.singleton_class.public_instance_methods(false).reject do |method| scope.singleton_class.public_instance_methods(false).reject do |method|
method.to_sym == :__class_init__ method.to_sym == :__class_init__
end.map do |name| end.each do |name|
new(scope, name) yield new(scope, name)
end end
end end
@ -46,24 +43,22 @@ module Mutant
scope.method(method_name) scope.method(method_name)
end end
# Check for stopping AST walk on branch # Check if node is matched
# #
# This method exist to protect against the # @param [Rubinius::AST::Node] node
# artifical edge case where DefineSingleton nodes
# with differend receivers exist on the same line.
#
# @param [Rubnius::AST::Node] node
# #
# @return [true] # @return [true]
# returns true when node should NOT be followed # returns true if node matches method
# #
# @return [false] # @return [false]
# returns false when node can be followed # returns false if node NOT matches method
# #
# @api private # @api private
# #
def stop?(node) def match?(node)
node.is_a?(Rubinius::AST::DefineSingleton) && !match_receiver?(node) node.line == source_line &&
node.class == Rubinius::AST::DefineSingleton &&
node.body.name == method_name && match_receiver?(node)
end end
# Check if receiver matches # Check if receiver matches
@ -106,22 +101,6 @@ module Mutant
node.name.to_s == context.unqualified_name node.name.to_s == context.unqualified_name
end 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

View file

@ -3,6 +3,8 @@ module Mutant
class Define < self class Define < self
handle(Rubinius::AST::Define) handle(Rubinius::AST::Define)
handle(Rubinius::AST::DefineSingleton)
handle(Rubinius::AST::DefineSingletonScope)
private private

View file

@ -38,7 +38,7 @@ module Mutant
if killer.fail? if killer.fail?
@io.puts(colorize(Color::RED, "!!! Uncovered Mutation !!!")) @io.puts(colorize(Color::RED, "!!! Uncovered Mutation !!!"))
differ = Differ.new(killer.original_source,killer.mutation_source) differ = Differ.new(killer.original_source,killer.mutation_source)
diff = color? ? differ.colorized_diff : color diff = color? ? differ.colorized_diff : differ.diff
@io.puts(diff) @io.puts(diff)
@io.puts @io.puts
end end

View file

@ -15,6 +15,14 @@ describe Mutant, 'method matching' do
let(:defaults) { {} } let(:defaults) { {} }
context 'on instance methods' do context 'on instance methods' do
def name(node)
node.name
end
def arguments(node)
node.arguments
end
let(:pattern) { 'Foo#bar' } let(:pattern) { 'Foo#bar' }
let(:defaults) do let(:defaults) do
{ {
@ -155,11 +163,19 @@ describe Mutant, 'method matching' do
let(:defaults) do let(:defaults) do
{ {
:scope => Foo, :scope => Foo,
:node_class => Rubinius::AST::DefineSingletonScope, :node_class => Rubinius::AST::DefineSingleton,
:method_arity => 0 :method_arity => 0
} }
end end
def name(node)
node.body.name
end
def arguments(node)
node.body.arguments
end
context 'when defined on self' do context 'when defined on self' do
let(:body) do let(:body) do
<<-RUBY <<-RUBY

View file

@ -8,11 +8,15 @@ describe Mutant, 'runner' do
end end
it 'allows to run mutant over a project' do it 'allows to run mutant over a project' do
output = StringIO.new
runner = Mutant::Runner.run( runner = Mutant::Runner.run(
:pattern => /\ATestApp::/, :pattern => /\ATestApp::/,
:killer => Mutant::Killer::Rspec, :killer => Mutant::Killer::Rspec,
:reporter => Mutant::Reporter::CLI.new($stdout) :reporter => Mutant::Reporter::CLI.new(output)
) )
runner.fail?.should be(true) runner.fail?.should be(true)
runner.errors.size.should be(22)
output.rewind
output.lines.grep(/Mutation/).size.should be(47)
end end
end end

View file

@ -18,7 +18,7 @@ shared_examples_for 'a method match' do
end end
it 'should have correct method name' do it 'should have correct method name' do
node.name.should eql(method_name) name(node).should eql(method_name)
end end
it 'should have correct line number' do it 'should have correct line number' do
@ -26,7 +26,7 @@ shared_examples_for 'a method match' do
end end
it 'should have correct arity' do it 'should have correct arity' do
node.arguments.required.length.should eql(method_arity) arguments(node).required.length.should eql(method_arity)
end end
it 'should have correct scope in context' do it 'should have correct scope in context' do

View file

@ -19,4 +19,23 @@ describe Mutant::Mutator, 'define' do
it_should_behave_like 'a mutator' it_should_behave_like 'a mutator'
end end
context 'define on singleton' do
let(:source) { 'def self.foo; self.bar; self.baz; end' }
let(:mutations) do
mutations = []
# Body presence mutations
mutations << 'def self.foo; bar; self.baz; end'
mutations << 'def self.foo; self.bar; baz; end'
# Body presence mutations
mutations << 'def self.foo; self.bar; end'
mutations << 'def self.foo; self.baz; end'
end
it_should_behave_like 'a mutator'
end
end end