Generalized filters

This commit is contained in:
Markus Schirp 2013-09-07 18:57:06 +02:00
parent 69851383e9
commit 7b0f5d78ff
13 changed files with 327 additions and 223 deletions

View file

@ -34,13 +34,14 @@ require 'mutant/singleton_methods'
require 'mutant/constants'
require 'mutant/support/method_object'
require 'mutant/random'
require 'mutant/filter'
require 'mutant/filter/attribute'
require 'mutant/filter/whitelist'
require 'mutant/filter/blacklist'
require 'mutant/mutator'
require 'mutant/mutation'
require 'mutant/mutation/evil'
require 'mutant/mutation/neutral'
require 'mutant/mutation/filter'
require 'mutant/mutation/filter/code'
require 'mutant/mutation/filter/whitelist'
require 'mutant/mutator/registry'
require 'mutant/mutator/util'
require 'mutant/mutator/util/array'

View file

@ -94,9 +94,9 @@ module Mutant
#
def filter
if @filters.empty?
Mutation::Filter::ALL
Filter::ALL
else
Mutation::Filter::Whitelist.new(@filters)
Filter::Whitelist.new(@filters)
end
end
@ -142,16 +142,14 @@ module Mutant
# Add mutation filter
#
# @param [Class<Mutant::Filter>] klass
#
# @param [String] filter
# @param [Class<Filter>] klass
#
# @return [undefined]
#
# @api private
#
def add_filter(klass, filter)
@filters << klass.new(filter)
def add_filter(klass, *arguments)
@filters << klass.new(*arguments)
end
# Parse the command-line options
@ -257,7 +255,7 @@ module Mutant
puts("mutant-#{Mutant::VERSION}")
Kernel.exit(0)
end.on('--code FILTER', 'Adds a code filter') do |filter|
add_filter(Mutation::Filter::Code, filter)
add_filter(Filter::Attribute, :code, filter)
end.on('--fail-fast', 'Fail fast') do
@fail_fast = true
end.on('-d', '--debug', 'Enable debugging output') do

74
lib/mutant/filter.rb Normal file
View file

@ -0,0 +1,74 @@
module Mutant
# Abstract base class for mutation/subject filters
class Filter
include Adamantium::Flat, AbstractType
extend DescendantsTracker
# Check for match
#
# @param [Object] object
#
# @return [true]
# if object is matched by filter
#
# @return [false]
# otherwise
#
# @api private
#
abstract_method :match?
# Build filter from string
#
# @param [String] notation
#
# @return [Filter]
# when can be build from string
#
# @return [nil]
# returns nil otherwise
#
# @api private
#
def self.build(notation)
descendants.each do |descendant|
filter = descendant.handle(notation)
return filter if filter
end
nil
end
# Return filter for handle
#
# @param [String] _notation
#
# @return [nil]
# returns nil
#
# @api private
#
def self.handle(_notation)
nil
end
# Mutation filter matching all mutations
Mutant.singleton_subclass_instance('ALL', self) do
# Test for match
#
# @pram [Mutation] _mutation
#
# @return [true]
# returns true
#
# @api private
#
def match?(_mutation)
true
end
end
end # Filter
end # Mutant

View file

@ -0,0 +1,64 @@
# encoding: utf-8
module Mutant
class Filter
# Base class for filters filtering on object attributes
class Attribute < self
include Concord.new(:attribute_name, :expectation)
private
# Return value for object
#
# @param [Object] object
#
# @return [Object]
#
# @api private
#
def value(object)
object.public_send(attribute_name)
end
class Equality < self
# Test for match
#
# @param [Object] object
#
# @return [true]
# if attribute value matches expectation
#
# @return [false]
# otherwise
#
# @api private
#
def match?(object)
value(object).eql?(value)
end
PATTERN = /\A(code):([a-f0-9]{1,6})\z/.freeze
# Test if class handles string
#
# @param [String] notation
#
# @return [Filter]
# if notation matches pattern
#
# @return [nil]
# otherwise
#
# @api private
#
def self.handle(notation)
match = PATTERN.match(notation)
return unless match
new(match[1].to_sym, match[2])
end
end # Code
end # Attribute
end # Filter
end # Mutant

View file

@ -0,0 +1,27 @@
# encoding: utf-8
module Mutant
class Filter
# Blacklist filter
class Blacklist < self
include Adamantium::Flat, Concord.new(:blacklist)
# Test for match
#
# @param [Object] object
#
# @return [true]
# if object matches blacklist
#
# @return [false]
# otherwise
#
# @api private
#
def match?(object)
blacklist.none? { |filter| filter.match?(object) }
end
end # Whitelist
end # Filter
end # Mutant

View file

@ -0,0 +1,27 @@
# encoding: utf-8
module Mutant
class Filter
# Mutaiton filter filtering in regexp match on mutation identification
class Regexp < self
include Concord::Public.new(:regexp)
# Test for match
#
# @param [Mutation] mutation
#
# @return [true]
# returns true if mutation identification is matched by regexp
#
# @return [false]
# returns false otherwise
#
# @api private
#
def match?(mutation)
!!(regexp =~ mutation.identification)
end
end # Regexp
end # Filter
end # Mutant

View file

@ -0,0 +1,28 @@
# encoding: utf-8
module Mutant
class Filter
# Whiltelist filter
class Whitelist < self
include Adamantium::Flat, Concord.new(:whitelist)
# Test for match
#
# @param [Object] object
#
# @return [true]
# if mutation matches whitelist
#
# @return [false]
# otherwise
#
# @api private
#
def match?(object)
whitelist.any? { |filter| filter.match?(object) }
end
end # Whitelist
end # Filter
end # Mutant

View file

@ -1,78 +0,0 @@
# encoding: utf-8
module Mutant
class Mutation
# Abstract filter for mutations
class Filter
include Adamantium::Flat, AbstractType
extend DescendantsTracker
# Check for match
#
# @param [Mutation] mutation
#
# @return [true]
# returns true if mutation is matched by filter
#
# @return [false]
# returns false otherwise
#
# @api private
#
abstract_method :match?
# Build filter from string
#
# @param [String] notation
#
# @return [Filter]
# returns filter when can be buld from string
#
# @return [nil]
# returns nil otherwise
#
# @api private
#
def self.build(notation)
descendants.each do |descendant|
filter = descendant.handle(notation)
return filter if filter
end
nil
end
# Return filter for handle
#
# @param [String] _notation
#
# @return [nil]
# returns nil
#
# @api private
#
def self.handle(_notation)
nil
end
# Mutation filter matching all mutations
Mutant.singleton_subclass_instance('ALL', self) do
# Test for match
#
# @pram [Mutation] _mutation
#
# @return [true]
# returns true
#
# @api private
#
def match?(_mutation)
true
end
end
end # Filter
end # Mutation
end # Mutant

View file

@ -1,49 +0,0 @@
# encoding: utf-8
module Mutant
class Mutation
class Filter
# Mutation filter that filters on mutation codes
class Code < self
include Concord::Public.new(:code)
# Test for match
#
# @param [Mutation] mutation
#
# @return [true]
# returns true if mutation code matches filter code
#
# @return [false]
# returns false otherwise
#
# @api private
#
def match?(mutation)
mutation.code.eql?(code)
end
PATTERN = /\Acode:([a-f0-9]{1,6})\z/.freeze
# Test if class handles string
#
# @param [String] notation
#
# @return [Filter]
# return code filter instance if notation matches pattern
#
# @return [nil]
# returns nil otherwise
#
# @api private
#
def self.handle(notation)
match = PATTERN.match(notation)
return unless match
new(match[1])
end
end # Code
end # Filter
end # Mutation
end # Mutant

View file

@ -1,29 +0,0 @@
# encoding: utf-8
module Mutant
class Mutation
class Filter
# Mutaiton filter filtering in regexp match on mutation identification
class Regexp < self
include Concord::Public.new(:regexp)
# Test for match
#
# @param [Mutation] mutation
#
# @return [true]
# returns true if mutation identification is matched by regexp
#
# @return [false]
# returns false otherwise
#
# @api private
#
def match?(mutation)
!!(regexp =~ mutation.identification)
end
end # Regexp
end # Filter
end # Mutation
end # Mutant

View file

@ -1,52 +0,0 @@
# encoding: utf-8
module Mutant
class Mutation
class Filter
# Whiltelist filter
class Whitelist < self
include Adamantium::Flat, Equalizer.new(:whitelist)
# Test for match
#
# @param [Mutation] mutation
#
# @return [true]
# returns true if mutation matches whitelist
#
# @return [false]
# returns false otherwise
#
# @api private
#
def match?(mutation)
@whitelist.any? { |filter| filter.match?(mutation) }
end
# Return whitelist
#
# @return [Enumerable<Filter>]
#
# @api private
#
attr_reader :whitelist
private
# Initalize white list
#
# @param [Enumerable<Filter>] whitelist
#
# @return [undefined]
#
# @api private
#
def initialize(whitelist)
@whitelist = whitelist
end
end # Whitelist
end # Filter
end # Mutation
end # Mutant

View file

@ -28,7 +28,7 @@ describe Mutant::CLI, '.new' do
end
# Defaults
let(:expected_filter) { Mutant::Mutation::Filter::ALL }
let(:expected_filter) { Mutant::Filter::ALL }
let(:expected_strategy) { Mutant::Strategy::Rspec.new(0) }
let(:expected_reporter) { Mutant::Reporter::CLI.new($stdout) }
@ -122,13 +122,13 @@ describe Mutant::CLI, '.new' do
let(:filters) do
[
Mutant::Mutation::Filter::Code.new('faa'),
Mutant::Mutation::Filter::Code.new('bbb'),
Mutant::Filter::Attribute.new(:code, 'faa'),
Mutant::Filter::Attribute.new(:code, 'bbb'),
]
end
let(:expected_matcher) { ns::Method.new(cache, 'TestApp::Literal#float') }
let(:expected_filter) { Mutant::Mutation::Filter::Whitelist.new(filters) }
let(:expected_filter) { Mutant::Filter::Whitelist.new(filters) }
it_should_behave_like 'a cli parser'
end

View file

@ -0,0 +1,93 @@
require 'spec_helper'
filter_helpers = proc do
let(:item_a) { double('Item A', :foo => 'bar') }
let(:item_b) { double('Item B', :foo => 'baz') }
let(:filter_a) do
item_a = self.item_a
Module.new do
define_singleton_method(:match?) do |item|
item == item_a
end
end
end
end
describe Mutant::Filter::Whitelist do
instance_eval(&filter_helpers)
let(:object) { described_class.new(whitelist) }
describe '#match?' do
subject { object.match?(item) }
context 'with empty whitelist' do
let(:whitelist) { [] }
it 'accepts all items' do
expect(object.match?(item_a)).to be(false)
expect(object.match?(item_b)).to be(false)
end
end
context 'with non empty whitelist' do
let(:whitelist) { [filter_a] }
context 'with whitelisted item' do
let(:item) { item_a }
it { should be(true) }
end
context 'with non whitelisted item' do
let(:item) { item_b }
it { should be(false) }
end
end
end
end
describe Mutant::Filter::Blacklist do
instance_eval(&filter_helpers)
let(:object) { described_class.new(whitelist) }
describe '#match?' do
subject { object.match?(item) }
context 'with empty whitelist' do
let(:whitelist) { [] }
it 'accepts all items' do
expect(object.match?(item_a)).to be(true)
expect(object.match?(item_b)).to be(true)
end
end
context 'with non empty whitelist' do
let(:whitelist) { [filter_a] }
context 'with whitelisted item' do
let(:item) { item_a }
it { should be(false) }
end
context 'with non whitelisted item' do
let(:item) { item_b }
it { should be(true) }
end
end
end
end
describe Mutant::Filter::Attribute::Equality do
instance_eval(&filter_helpers)
let(:object) { described_class.new(attribute_name, expected_value) }
end