From 7b0f5d78ff74ece0f7161f4cc4c4eccb6e171ebe Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 7 Sep 2013 18:57:06 +0200 Subject: [PATCH] Generalized filters --- lib/mutant.rb | 7 +- lib/mutant/cli.rb | 14 ++- lib/mutant/filter.rb | 74 +++++++++++++++ lib/mutant/filter/attribute.rb | 64 +++++++++++++ lib/mutant/filter/blacklist.rb | 27 ++++++ lib/mutant/filter/regexp.rb | 27 ++++++ lib/mutant/filter/whitelist.rb | 28 ++++++ lib/mutant/mutation/filter.rb | 78 ---------------- lib/mutant/mutation/filter/code.rb | 49 ---------- lib/mutant/mutation/filter/regexp.rb | 29 ------ lib/mutant/mutation/filter/whitelist.rb | 52 ----------- .../unit/mutant/cli/class_methods/new_spec.rb | 8 +- spec/unit/mutant/filter_spec.rb | 93 +++++++++++++++++++ 13 files changed, 327 insertions(+), 223 deletions(-) create mode 100644 lib/mutant/filter.rb create mode 100644 lib/mutant/filter/attribute.rb create mode 100644 lib/mutant/filter/blacklist.rb create mode 100644 lib/mutant/filter/regexp.rb create mode 100644 lib/mutant/filter/whitelist.rb delete mode 100644 lib/mutant/mutation/filter.rb delete mode 100644 lib/mutant/mutation/filter/code.rb delete mode 100644 lib/mutant/mutation/filter/regexp.rb delete mode 100644 lib/mutant/mutation/filter/whitelist.rb create mode 100644 spec/unit/mutant/filter_spec.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index 25708235..6d606ebf 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -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' diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index e55ca8c6..a226839d 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -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] klass - # - # @param [String] filter + # @param [Class] 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 diff --git a/lib/mutant/filter.rb b/lib/mutant/filter.rb new file mode 100644 index 00000000..03a490bc --- /dev/null +++ b/lib/mutant/filter.rb @@ -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 diff --git a/lib/mutant/filter/attribute.rb b/lib/mutant/filter/attribute.rb new file mode 100644 index 00000000..63aaa13d --- /dev/null +++ b/lib/mutant/filter/attribute.rb @@ -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 diff --git a/lib/mutant/filter/blacklist.rb b/lib/mutant/filter/blacklist.rb new file mode 100644 index 00000000..30cb5961 --- /dev/null +++ b/lib/mutant/filter/blacklist.rb @@ -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 diff --git a/lib/mutant/filter/regexp.rb b/lib/mutant/filter/regexp.rb new file mode 100644 index 00000000..8f9fa541 --- /dev/null +++ b/lib/mutant/filter/regexp.rb @@ -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 diff --git a/lib/mutant/filter/whitelist.rb b/lib/mutant/filter/whitelist.rb new file mode 100644 index 00000000..b90c9478 --- /dev/null +++ b/lib/mutant/filter/whitelist.rb @@ -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 diff --git a/lib/mutant/mutation/filter.rb b/lib/mutant/mutation/filter.rb deleted file mode 100644 index ca9f5b55..00000000 --- a/lib/mutant/mutation/filter.rb +++ /dev/null @@ -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 diff --git a/lib/mutant/mutation/filter/code.rb b/lib/mutant/mutation/filter/code.rb deleted file mode 100644 index 7d046d41..00000000 --- a/lib/mutant/mutation/filter/code.rb +++ /dev/null @@ -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 diff --git a/lib/mutant/mutation/filter/regexp.rb b/lib/mutant/mutation/filter/regexp.rb deleted file mode 100644 index fcf4d0a4..00000000 --- a/lib/mutant/mutation/filter/regexp.rb +++ /dev/null @@ -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 diff --git a/lib/mutant/mutation/filter/whitelist.rb b/lib/mutant/mutation/filter/whitelist.rb deleted file mode 100644 index 4f056a28..00000000 --- a/lib/mutant/mutation/filter/whitelist.rb +++ /dev/null @@ -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] - # - # @api private - # - attr_reader :whitelist - - private - - # Initalize white list - # - # @param [Enumerable] whitelist - # - # @return [undefined] - # - # @api private - # - def initialize(whitelist) - @whitelist = whitelist - end - - end # Whitelist - end # Filter - end # Mutation -end # Mutant diff --git a/spec/unit/mutant/cli/class_methods/new_spec.rb b/spec/unit/mutant/cli/class_methods/new_spec.rb index b712efcb..2b815670 100644 --- a/spec/unit/mutant/cli/class_methods/new_spec.rb +++ b/spec/unit/mutant/cli/class_methods/new_spec.rb @@ -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 diff --git a/spec/unit/mutant/filter_spec.rb b/spec/unit/mutant/filter_spec.rb new file mode 100644 index 00000000..28adc4d6 --- /dev/null +++ b/spec/unit/mutant/filter_spec.rb @@ -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