1
0
Fork 0

Cleanup crashes and invalid mutants

This commit is contained in:
Markus Schirp 2012-12-07 23:27:21 +01:00
parent ca56ef3f0a
commit b5430e2000
27 changed files with 291 additions and 162 deletions

View file

@ -1,5 +1,10 @@
# v0.2.1 xxx
* [fixed] Crash on unavailable source location
* [fixed] Incorrect handling of if and unless statements
* [fixed] Expand Foo#initialize to spec/unit/foo in rspec dm2 strategy
* [fixed] Correctly expand [] to element_reader_spec.rb in rspec dm2 strategy
* [fixed] Correctly expand []= to element_writer_spec.rb in rspec dm2 strategy
* [fixed] Correctly expand foo= to foo_writer_spec.rb in rspec dm2 strategy
[Compare v0.2.0..v0.2.1](https://github.com/mbj/mutant/compare/v0.2.0...v0.2.1)

View file

@ -2,5 +2,5 @@ source 'https://rubygems.org'
gemspec
gem 'devtools', :git => 'https://github.com/mbj/devtools.git', :branch => :'rspec-2-mutant'
gem 'devtools', :git => 'https://github.com/mbj/devtools.git'
eval(File.read(File.join(File.dirname(__FILE__),'Gemfile.devtools')))

View file

@ -41,6 +41,8 @@ module Mutant
self
end
PID = Process.pid
end
require 'mutant/support/method_object'
@ -84,8 +86,8 @@ require 'mutant/matcher/object_space'
require 'mutant/matcher/method'
require 'mutant/matcher/method/singleton'
require 'mutant/matcher/method/instance'
require 'mutant/matcher/method/classifier'
require 'mutant/matcher/scope_methods'
require 'mutant/matcher/method/classifier'
require 'mutant/killer'
require 'mutant/killer/static'
require 'mutant/killer/rspec'

View file

@ -28,6 +28,12 @@ module Mutant
#
def each(&block)
return to_enum unless block_given?
unless source_location
$stderr.puts "#{method.inspect} does not have source location unable to emit matcher"
return self
end
subject.tap do |subject|
yield subject if subject
end
@ -35,6 +41,14 @@ module Mutant
self
end
# Return method
#
# @return [UnboundMethod, Method]
#
# @api private
#
attr_reader :method
# Return scope
#
# @return [Class|Module]
@ -43,21 +57,15 @@ module Mutant
#
attr_reader :scope
# Return context
#
# @return [Context::Scope]
#
# @api private
#
attr_reader :context
# Return method name
#
# @return [String]
#
# @api private
#
attr_reader :method_name
def method_name
method.name
end
# Test if method is public
#
@ -76,26 +84,27 @@ module Mutant
# Initialize method filter
#
# @param [Class|Module] scope
# @param [Symbol] method_name
# @param [Method, UnboundMethod] method
#
# @return [undefined]
#
# @api private
#
def initialize(scope, method_name)
@scope, @method_name = scope, method_name.to_sym
@context = Context::Scope.build(scope, source_path)
def initialize(scope, method)
@scope, @method = scope, method
# FIXME: cache public private should not be needed, loader should not override visibility! (But does currently) :(
public?
end
# Return method
# Return context
#
# @return [UnboundMethod, Method]
# @return [Context::Scope]
#
# @api private
#
abstract_method :method
def context
Context::Scope.build(scope, source_path)
end
# Return full ast
#

View file

@ -6,15 +6,15 @@ module Mutant
include Adamantium::Flat
TABLE = {
'.' => Matcher::Method::Singleton,
'#' => Matcher::Method::Instance
'.' => Matcher::ScopeMethods::Singleton,
'#' => Matcher::ScopeMethods::Instance
}.freeze
SCOPE_FORMAT = /\A([^#.]+)(\.|#)(.+)\z/.freeze
# Positions of captured regexp groups
# Freezing fixnums to avoid their singleton classes are patched.
SCOPE_NAME_POSITION = 1.freeze
SCOPE_NAME_POSITION = 1.freeze
SCOPE_SYMBOL_POSITION = 2.freeze
METHOD_NAME_POSITION = 3.freeze
@ -45,8 +45,22 @@ module Mutant
# @api private
#
def matcher
matcher_class.new(scope, method_name)
scope_matcher.matcher.new(scope, method)
end
memoize :matcher
# Return method
#
# @return [Method, UnboundMethod]
#
# @api private
#
def method
scope_matcher.methods.detect do |method|
method.name == method_name
end || raise("Cannot find #{method_name} for #{scope}")
end
memoize :method, :freezer => :noop
# Return match
#
@ -117,9 +131,10 @@ module Mutant
#
# @api private
#
def matcher_class
TABLE.fetch(scope_symbol)
def scope_matcher
TABLE.fetch(scope_symbol).new(scope)
end
memoize :scope_matcher
end
end
end

View file

@ -50,17 +50,6 @@ module Mutant
node.name == method_name
end
# Return method instance
#
# @return [UnboundMethod]
#
# @api private
#
def method
scope.instance_method(method_name)
end
memoize :method, :freezer => :noop
end
end
end

View file

@ -33,17 +33,6 @@ module Mutant
private
# Return method instance
#
# @return [UnboundMethod]
#
# @api private
#
def method
scope.method(method_name)
end
memoize :method, :freezer => :noop
# Test for node match
#
# @param [Rubinius::AST::Node] node

View file

@ -24,6 +24,7 @@ module Mutant
#
def each(&block)
return to_enum unless block_given?
methods.each do |method|
emit_matches(method, &block)
end
@ -31,6 +32,29 @@ module Mutant
self
end
# Return methods
#
# @return [Enumerable<Method, UnboundMethod>]
#
# @api private
#
def methods
method_names.map do |name|
access(name)
end
end
memoize :methods
# Return method matcher class
#
# @return [Class:Matcher::Method]
#
# @api private
#
def matcher
self.class::MATCHER
end
private
# Initialize object
@ -47,7 +71,7 @@ module Mutant
# Emit matches for method
#
# @param [UnboundMethod] method
# @param [UnboundMethod, Method] method
#
# @return [undefined]
#
@ -59,22 +83,29 @@ module Mutant
end
end
abstract_method :methods
# Return method matcher class
# Return method names
#
# @return [Class:Matcher::Method]
# @return [Enumerable<Symbol>]
#
# @api private
#
def matcher
self.class::MATCHER
end
abstract_method :method_names
class Singleton < self
MATCHER = Mutant::Matcher::Method::Singleton
# Return method for name
#
# @param [Symbol] method_name
#
# @return [Method]
#
# @api private
#
def access(method_name)
scope.method(method_name)
end
private
# Return singleton methods defined on scope
@ -85,7 +116,7 @@ module Mutant
#
# @api private
#
def methods
def method_names
singleton_class = scope.singleton_class
names =
@ -93,7 +124,7 @@ module Mutant
singleton_class.private_instance_methods(false) +
singleton_class.protected_instance_methods(false)
names.map(&:to_sym).sort.reject do |name|
names.sort.reject do |name|
name.to_sym == :__class_init__
end
end
@ -102,6 +133,18 @@ module Mutant
class Instance < self
MATCHER = Mutant::Matcher::Method::Instance
# Return method for name
#
# @param [Symbol] method_name
#
# @return [UnboundMethod]
#
# @api private
#
def access(method_name)
scope.instance_method(method_name)
end
private
# Return instance methods names of scope
@ -110,7 +153,7 @@ module Mutant
#
# @return [Enumerable<Symbol>]
#
def methods
def method_names
scope = self.scope
return [] unless scope.kind_of?(Module)
@ -119,7 +162,7 @@ module Mutant
scope.private_instance_methods(false) +
scope.protected_instance_methods(false)
names.uniq.map(&:to_sym).sort
names.uniq.sort
end
end
end

View file

@ -18,6 +18,26 @@ module Mutant
array = input.array
emit_attribute_mutations(:array)
end
# Test if node is new
#
# FIXME: Remove this hack and make sure empty bodies are not generated
#
# @param [Rubinius::AST::Node]
#
# @return [true]
# if node is new
#
# @return [false]
# otherwise
#
def new?(node)
if node.array.empty?
node.array << new_nil
end
super
end
end
end
end

View file

@ -16,8 +16,8 @@ module Mutant
#
def dispatch
emit_attribute_mutations(:condition)
emit_attribute_mutations(:body)
emit_attribute_mutations(:else) if node.else
emit_attribute_mutations(:body) if node.body.class != Rubinius::AST::NilLiteral
emit_attribute_mutations(:else) if node.else.class != Rubinius::AST::NilLiteral
emit_inverted_condition
emit_deleted_if_branch
emit_deleted_else_branch

View file

@ -49,7 +49,7 @@ module Mutant
#
def emit_finish_mutations
finish = node.finish
emit_self(negative_infinity, finish)
#emit_self(negative_infinity, finish)
emit_self(nan, finish)
end

View file

@ -11,7 +11,7 @@ module Mutant
# Return filename pattern
#
# @return [String]
# @return [Enumerable<String>]
#
# @api private
#
@ -25,17 +25,17 @@ module Mutant
# Return file name pattern for mutation
#
# @return [Mutation]
# @return [Enumerable<String>]
#
# @api private
#
def self.spec_files(mutation)
Dir['spec/unit/**/*_spec.rb']
['spec/unit']
end
end
# Run all integration specs per mutation
class Unit < self
class Integration < self
# Return file name pattern for mutation
#

View file

@ -63,13 +63,25 @@ module Mutant
# @api private
#
def spec_file
matcher.method_name.to_s.
method_name.to_s.
gsub(/\A\[\]\z/, 'element_reader').
gsub(/\A\[\]=\z/, 'element_writer').
gsub(/\?\z/, '_predicate').
gsub(/=\z/, '_writer').
gsub(/!\z/, '_bang') + '_spec.rb'
end
memoize :spec_file
# Return method name
#
# @return [Symbol]
#
# @api private
#
def method_name
matcher.method_name
end
# Return glob expression
#
# @return [String]
@ -77,6 +89,8 @@ module Mutant
# @api private
#
def glob_expression
return base_path if method_name == :initialize
if mutation.subject.matcher.public?
"#{base_path}/#{spec_file}"
else

View file

@ -1,16 +0,0 @@
shared_examples_for 'a method filter parse result' do
before do
expected_class.stub(:new => response)
end
let(:response) { mock('Response') }
it { should be(response) }
it 'should initialize method filter with correct arguments' do
expected_class.should_receive(:new).with(TestApp::Literal, :string).and_return(response)
subject
end
end

View file

@ -15,25 +15,31 @@ shared_examples_for 'a mutator' do
it { should be_instance_of(to_enum.class) }
let(:expected_mutations) do
mutations.map do |mutation|
if mutation.respond_to?(:to_ast)
mutation.to_ast.to_sexp
else
mutation
end
end.to_set
unless instance_methods.include?(:expected_mutations)
let(:expected_mutations) do
mutations.map do |mutation|
case mutation
when String
mutation.to_ast
when Rubinius::AST::Node
mutation
else
raise
end
end.map do |node|
ToSource.to_source(node)
end.to_set
end
end
it 'generates the expected mutations' do
generated = self.subject.map(&:to_sexp).to_set
generated = self.subject.map { |mutation| ToSource.to_source(mutation) }.to_set
missing = (expected_mutations - generated).to_a
unexpected = (generated - expected_mutations).to_a
unless generated == expected_mutations
message = "Missing mutations: %s\nUnexpected mutations: %s" % [missing, unexpected].map(&:inspect)
fail message
fail "Missing mutations:\n%s\nUnexpected mutations:\n%s" % [missing.join("\n----\n"), unexpected.join("\n----\n")]
end
end
end

View file

@ -3,25 +3,43 @@ require 'spec_helper'
describe Mutant::Matcher::Method::Classifier, '.run' do
subject { described_class.run(input) }
context 'with explicit toplevel scope' do
let(:input) { '::TestApp::Literal#string' }
let(:expected_class) { Mutant::Matcher::Method::Instance }
shared_examples_for 'Mutant::Matcher::Method::Classifier.run' do
before do
expected_class.stub(:new => response)
end
it_should_behave_like 'a method filter parse result'
let(:response) { :Response }
it { should be(response) }
it 'should initialize method filter with correct arguments' do
expected_class.should_receive(:new).with(TestApp::Literal, expected_method).and_return(response)
subject
end
end
context 'with explicit toplevel scope' do
let(:input) { '::TestApp::Literal#string' }
let(:expected_class) { Mutant::Matcher::Method::Instance }
let(:expected_method) { TestApp::Literal.instance_method(:string) }
it_should_behave_like 'Mutant::Matcher::Method::Classifier.run'
end
context 'with instance method notation' do
let(:input) { 'TestApp::Literal#string' }
let(:expected_class) { Mutant::Matcher::Method::Instance }
let(:input) { 'TestApp::Literal#string' }
let(:expected_method) { TestApp::Literal.instance_method(:string) }
let(:expected_class) { Mutant::Matcher::Method::Instance }
it_should_behave_like 'a method filter parse result'
it_should_behave_like 'Mutant::Matcher::Method::Classifier.run'
end
context 'with singleton method notation' do
let(:input) { 'TestApp::Literal.string' }
let(:expected_class) { Mutant::Matcher::Method::Singleton }
let(:input) { 'TestApp::Literal.string' }
let(:expected_method) { TestApp::Literal.method(:string) }
let(:expected_class) { Mutant::Matcher::Method::Singleton }
it_should_behave_like 'a method filter parse result'
it_should_behave_like 'Mutant::Matcher::Method::Classifier.run'
end
context 'with invalid notation' do

View file

@ -1,11 +0,0 @@
require 'spec_helper'
describe Mutant::Matcher::Method, '#method' do
subject { object.send(:method) }
let(:object) { described_class.allocate }
it 'should raise error' do
expect { subject }.to raise_error(NotImplementedError, 'Mutant::Matcher::Method#method is not implemented')
end
end

View file

@ -9,26 +9,27 @@ describe Mutant::Mutator, 'block' do
mutations = []
# Mutation of each statement in block
mutations << "foo\nself.bar"
mutations << "self.foo\nbar"
mutations << "foo\nself.bar".to_ast
mutations << "self.foo\nbar".to_ast
## Remove statement in block
mutations << [:block, 'self.foo'.to_sexp]
mutations << [:block, 'self.bar'.to_sexp]
mutations << [:block]
# Remove statement in block
mutations << Rubinius::AST::Block.new(1, ['self.foo'.to_ast])
mutations << Rubinius::AST::Block.new(1, ['self.bar'.to_ast])
mutations << Rubinius::AST::Block.new(1, ['nil'.to_ast])
end
it_should_behave_like 'a mutator'
end
context 'with one statement' do
let(:node) { Rubinius::AST::Block.new(1, ['self.foo'.to_ast]) }
let(:mutations) do
mutations = []
mutations << [:block, 'foo'.to_sexp]
mutations << [:block]
mutations << Rubinius::AST::Block.new(1, ['foo'.to_ast])
mutations << Rubinius::AST::Block.new(1, ['nil'.to_ast])
end
it_should_behave_like 'a mutator'

View file

@ -17,7 +17,7 @@ describe Mutant::Mutator, 'define' do
mutations << 'def foo; self.bar; end'
# Remove all statements
mutations << [:defn, :foo, [:args], [:scope, [:block]]]
mutations << 'def foo; end'
end
it_should_behave_like 'a mutator'
@ -38,7 +38,7 @@ describe Mutant::Mutator, 'define' do
mutations << 'def self.foo; self.baz; end'
# Remove all statements
mutations << [:defs, [:self], :foo, [:args], [:scope, [:block]]]
mutations << 'def self.foo; end'
end
it_should_behave_like 'a mutator'

View file

@ -1,30 +1,60 @@
require 'spec_helper'
describe Mutant::Mutator, 'if statement' do
let(:source) { 'if self.condition; true; else false; end' }
let(:mutations) do
mutants = []
context 'if with two branches' do
let(:source) { 'if self.condition; true; else false; end' }
# mutations of condition
mutants << 'if condition; true; else false; end'
let(:mutations) do
mutants = []
mutants << 'if !self.condition; true; else false; end'
# mutations of condition
mutants << 'if condition; true; else false; end'
# Deleted else branch
mutants << 'if self.condition; true end'
mutants << 'if !self.condition; true; else false; end'
# Deleted if branch with promoting else branch to if branch
mutants << 'if self.condition; false end'
# Deleted else branch
mutants << 'if self.condition; true end'
# mutations of body
mutants << 'if self.condition; false; else false; end'
mutants << 'if self.condition; nil; else false; end'
# Deleted if branch with promoting else branch to if branch
mutants << 'if self.condition; false end'
# mutations of else body
mutants << 'if self.condition; true; else true; end'
mutants << 'if self.condition; true; else nil; end'
# mutations of body
mutants << 'if self.condition; false; else false; end'
mutants << 'if self.condition; nil; else false; end'
# mutations of else body
mutants << 'if self.condition; true; else true; end'
mutants << 'if self.condition; true; else nil; end'
end
it_should_behave_like 'a mutator'
end
it_should_behave_like 'a mutator'
context 'unless with one branch' do
let(:source) { 'unless condition; true; end' }
let(:mutations) do
mutants = []
mutants << 'unless !condition; true; end'
mutants << 'if condition; true; end'
mutants << 'unless condition; false; end'
mutants << 'unless condition; nil; end'
end
it_should_behave_like 'a mutator'
end
context 'if with one branch' do
let(:source) { 'if condition; true; end' }
let(:mutations) do
mutants = []
mutants << 'if !condition; true; end'
mutants << 'if condition; false; end'
mutants << 'if condition; nil; end'
end
it_should_behave_like 'a mutator'
end
end

View file

@ -7,7 +7,7 @@ describe Mutant::Mutator::Node::Literal, 'array' do
mutations = []
# Literal replaced with nil
mutations << [:nil]
mutations << 'nil'
# Mutation of each element in array
mutations << '[nil, false]'

View file

@ -7,7 +7,7 @@ describe Mutant::Mutator::Node::Literal, 'empty array' do
mutations = []
# Literal replaced with nil
mutations << [:nil]
mutations << 'nil'
# Extra element
mutations << '[nil]'

View file

@ -6,7 +6,7 @@ describe Mutant::Mutator::Node::Literal, 'fixnum' do
let(:source) { '10' }
let(:mutations) do
%W(nil 0 1 #{random_fixnum}) << [:lit, -10]
%W(nil 0 1 #{random_fixnum} -10)
end
before do

View file

@ -11,8 +11,8 @@ describe Mutant::Mutator::Node::Literal, 'float' do
mutations << random_float.to_s
mutations << '0.0/0.0'
mutations << '1.0/0.0'
mutations << [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]]
mutations << [:lit, -10.0]
mutations << '-1.0 / 0.0'
mutations << '-10.0'
end
let(:random_float) { 7.123 }

View file

@ -7,27 +7,27 @@ describe Mutant::Mutator::Node::Literal, 'hash' do
mutations = []
# Literal replaced with nil
mutations << [:nil]
mutations << 'nil'
# Mutation of each key and value in hash
mutations << [:hash, [:false ], [:true ], [:false], [:false]]
mutations << [:hash, [:nil ], [:true ], [:false], [:false]]
mutations << [:hash, [:true ], [:false], [:false], [:false]]
mutations << [:hash, [:true ], [:nil ], [:false], [:false]]
mutations << [:hash, [:true ], [:true ], [:true ], [:false]]
mutations << [:hash, [:true ], [:true ], [:nil ], [:false]]
mutations << [:hash, [:true ], [:true ], [:false], [:true ]]
mutations << [:hash, [:true ], [:true ], [:false], [:nil ]]
mutations << '{ false => true , false => false }'
mutations << '{ nil => true , false => false }'
mutations << '{ true => false , false => false }'
mutations << '{ true => nil , false => false }'
mutations << '{ true => true , true => false }'
mutations << '{ true => true , nil => false }'
mutations << '{ true => true , false => true }'
mutations << '{ true => true , false => nil }'
# Remove each key once
mutations << [:hash, [:true ], [:true ]]
mutations << [:hash, [:false ], [:false ]]
mutations << '{ true => true }'
mutations << '{ false => false }'
# Empty hash
mutations << [:hash]
mutations << '{}'
# Extra element
mutations << [:hash, [:true ], [:true ], [:false], [:false ], [:nil], [:nil] ]
mutations << '{true => true, false => false, nil => nil}'
end
it_should_behave_like 'a mutator'

View file

@ -9,7 +9,7 @@ describe Mutant::Mutator::Node::Literal, 'range' do
mutations << 'nil'
mutations << '1...100'
mutations << '(0.0/0.0)..100'
mutations << [:dot2, [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]], [:lit, 100]]
#mutations << [:dot2, [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]], [:lit, 100]]
mutations << '1..(1.0/0.0)'
mutations << '1..(0.0/0.0)'
end
@ -25,7 +25,7 @@ describe Mutant::Mutator::Node::Literal, 'range' do
mutations << 'nil'
mutations << '1..100'
mutations << '(0.0/0.0)...100'
mutations << [:dot3, [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]], [:lit, 100]]
#mutations << [:dot3, [:negate, [:call, [:lit, 1.0], :/, [:arglist, [:lit, 0.0]]]], [:lit, 100]]
mutations << '1...(1.0/0.0)'
mutations << '1...(0.0/0.0)'
end

View file

@ -16,6 +16,21 @@ describe Mutant::Strategy::Rspec::ExampleLookup, '#spec_file' do
it { should be_frozen }
end
context 'with element reader method' do
let(:method_name) { '[]' }
let(:expected_spec_file) { 'element_reader_spec.rb' }
it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file'
end
context 'with element writer method' do
let(:method_name) { '[]=' }
let(:expected_spec_file) { 'element_writer_spec.rb' }
it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file'
end
context 'with writer method' do
let(:method_name) { 'foo=' }
let(:expected_spec_file) { 'foo_writer_spec.rb' }