Reduce and specify behavior of matcher compiler
This commit is contained in:
parent
09edb2573e
commit
dbafc31462
7 changed files with 143 additions and 74 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 1045
|
||||
total_score: 1053
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,22 +24,6 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Build matcher chain
|
||||
#
|
||||
# @param [Enumerable<Matcher>] 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
96
spec/unit/mutant/matcher/compiler_spec.rb
Normal file
96
spec/unit/mutant/matcher/compiler_spec.rb
Normal file
|
@ -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
|
Loading…
Reference in a new issue