Generalized filters
This commit is contained in:
parent
69851383e9
commit
7b0f5d78ff
13 changed files with 327 additions and 223 deletions
|
@ -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'
|
||||
|
|
|
@ -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
74
lib/mutant/filter.rb
Normal 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
|
64
lib/mutant/filter/attribute.rb
Normal file
64
lib/mutant/filter/attribute.rb
Normal 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
|
27
lib/mutant/filter/blacklist.rb
Normal file
27
lib/mutant/filter/blacklist.rb
Normal 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
|
27
lib/mutant/filter/regexp.rb
Normal file
27
lib/mutant/filter/regexp.rb
Normal 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
|
28
lib/mutant/filter/whitelist.rb
Normal file
28
lib/mutant/filter/whitelist.rb
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
93
spec/unit/mutant/filter_spec.rb
Normal file
93
spec/unit/mutant/filter_spec.rb
Normal 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
|
Loading…
Add table
Reference in a new issue