diff --git a/config/flay.yml b/config/flay.yml index 01af296a..2301bdc6 100644 --- a/config/flay.yml +++ b/config/flay.yml @@ -1,3 +1,3 @@ --- threshold: 18 -total_score: 1045 +total_score: 1053 diff --git a/lib/mutant/expression.rb b/lib/mutant/expression.rb index 79ccc5b8..00f98a33 100644 --- a/lib/mutant/expression.rb +++ b/lib/mutant/expression.rb @@ -17,7 +17,10 @@ module Mutant REGISTRY = {} + # Error raised on invalid expressions class InvalidExpressionError < RuntimeError; end + + # Error raised on ambigous expressions class AmbigousExpressionError < RuntimeError; end # Initialize expression diff --git a/lib/mutant/matcher/chain.rb b/lib/mutant/matcher/chain.rb index 225aa8b9..7e0e2a96 100644 --- a/lib/mutant/matcher/chain.rb +++ b/lib/mutant/matcher/chain.rb @@ -24,22 +24,6 @@ module Mutant self end - # Build matcher chain - # - # @param [Enumerable] matchers - # - # @return [Matcher] - # - # @api private - # - def self.build(matchers) - if matchers.length.equal?(1) - return matchers.first - end - - new(matchers) - end - end # Chain end # Matcher end # Mutant diff --git a/lib/mutant/matcher/compiler.rb b/lib/mutant/matcher/compiler.rb index ee8d0675..4e560d09 100644 --- a/lib/mutant/matcher/compiler.rb +++ b/lib/mutant/matcher/compiler.rb @@ -12,23 +12,51 @@ module Mutant # @api private # def result - matchers = config.match_expressions.map(&method(:matcher)) - - if matchers.empty? - return Matcher::Null.new - end - - matcher = Matcher::Chain.build(matchers) - - if predicate - Matcher::Filter.new(matcher, predicate) - else - matcher - end + Filter.new( + Chain.build(config.match_expressions.map(&method(:matcher))), + predicate + ) end + # Subject expression prefix predicate + class SubjectPrefix + include Concord.new(:expression) + + # Test if subject expression is matched by prefix + # + # @return [Boolean] + # + # @api private + # + def call(subject) + expression.prefix?(subject.expression) + end + + end # SubjectPrefix + private + # Return predicate + # + # @return [#call] + # + # @api private + # + def predicate + if subject_selector && subject_rejector + Morpher::Evaluator::Predicate::Boolean::And.new([ + subject_selector, + Morpher::Evaluator::Predicate::Negation.new(subject_rejector) + ]) + elsif subject_selector + subject_selector + elsif subject_rejector + Morpher::Evaluator::Predicate::Negation.new(subject_rejector) + else + Morpher::Evaluator::Predicate::Tautology.new + end + end + # Return subject selector # # @return [#call] @@ -47,31 +75,6 @@ module Mutant Morpher::Evaluator::Predicate::Boolean::Or.new(selectors) if selectors.any? end - # Return predicate - # - # @return [#call] - # if filter is needed - # - # @return [nil] - # othrwise - # - # @api private - # - def predicate - if subject_selector && subject_rejector - Morpher::Evaluator::Predicate::Boolean::And.new([ - subject_selector, - Morpher::Evaluator::Predicate::Negation.new(subject_rejector) - ]) - elsif subject_selector - subject_selector - elsif subject_rejector - Morpher::Evaluator::Predicate::Negation.new(subject_rejector) - else - nil - end - end - # Return subject rejector # # @return [#call] @@ -83,9 +86,7 @@ module Mutant # @api private # def subject_rejector - rejectors = config.subject_ignores.map(&method(:matcher)).flat_map(&:to_a).map do |subject| - Morpher.compile(s(:eql, s(:attribute, :identification), s(:static, subject.identification))) - end + rejectors = config.subject_ignores.map(&SubjectPrefix.method(:new)) Morpher::Evaluator::Predicate::Boolean::Or.new(rejectors) if rejectors.any? end diff --git a/lib/mutant/matcher/null.rb b/lib/mutant/matcher/null.rb index cde09400..a4cc3fd1 100644 --- a/lib/mutant/matcher/null.rb +++ b/lib/mutant/matcher/null.rb @@ -2,7 +2,7 @@ module Mutant class Matcher # A null matcher, that does not match any subjects class Null < self - include Equalizer.new + include Concord.new # Enumerate subjects # diff --git a/spec/unit/mutant/matcher/chain_spec.rb b/spec/unit/mutant/matcher/chain_spec.rb index 90f0f6c7..9f4cdd53 100644 --- a/spec/unit/mutant/matcher/chain_spec.rb +++ b/spec/unit/mutant/matcher/chain_spec.rb @@ -42,19 +42,4 @@ describe Mutant::Matcher::Chain do it_should_behave_like 'an idempotent method' end - - describe '.build' do - subject { described_class.build(matchers) } - - context 'when one matcher given' do - let(:matchers) { [double('Matcher A')] } - it { should be(matchers.first) } - end - - context 'when matchers given' do - let(:matchers) { [double('Matcher A'), double('Matcher B')] } - it { should eql(described_class.new(matchers)) } - end - end - end diff --git a/spec/unit/mutant/matcher/compiler_spec.rb b/spec/unit/mutant/matcher/compiler_spec.rb new file mode 100644 index 00000000..5b3c1ba9 --- /dev/null +++ b/spec/unit/mutant/matcher/compiler_spec.rb @@ -0,0 +1,96 @@ +require 'spec_helper' + +describe Mutant::Matcher::Compiler do + let(:object) { described_class } + + let(:env) { Fixtures::TEST_ENV } + + let(:expression_a) { Mutant::Expression.parse('Foo*') } + let(:expression_b) { Mutant::Expression.parse('Bar*') } + + let(:matcher_a) { expression_a.matcher(env) } + let(:matcher_b) { expression_b.matcher(env) } + + let(:expected_matcher) do + Mutant::Matcher::Filter.new(expected_positive_matcher, expected_predicate) + end + + let(:expected_predicate) do + Morpher.compile(s(:true)) + end + + describe '.call' do + subject { object.call(env, matcher_config.update(attributes)) } + + let(:matcher_config) { Mutant::Matcher::Config::DEFAULT } + + + context 'on empty config' do + let(:attributes) { {} } + + let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([]) } + + it { should eql(expected_matcher) } + end + + context 'on config with match expression' do + context 'and no filter' do + let(:attributes) do + { match_expressions: [expression_a] } + end + + let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a]) } + + it { should eql(expected_matcher) } + end + + context 'and a subject filter' do + let(:attributes) do + { + match_expressions: [expression_a], + subject_ignores: [expression_b] + } + end + + let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a]) } + + let(:expected_predicate) { + Morpher::Evaluator::Predicate::Negation.new( + Morpher::Evaluator::Predicate::Boolean::Or.new([ + described_class::SubjectPrefix.new(expression_b) + ]) + ) + } + + it { should eql(expected_matcher) } + end + + context 'and an attribute filter' do + let(:attributes) do + { + match_expressions: [expression_a], + subject_selects: [[:code, 'foo']] + } + end + + let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a]) } + + let(:expected_predicate) { + Morpher::Evaluator::Predicate::Boolean::Or.new([ + Morpher.compile(s(:eql, s(:attribute, :code), s(:static, 'foo'))) + ]) + } + + it { should eql(expected_matcher) } + end + end + + context 'on config with multiple match expressions' do + let(:attributes) do + { match_expressions: [expression_a, expression_b] } + end + + let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a, matcher_b]) } + end + end +end