Reduce and specify behavior of matcher compiler

This commit is contained in:
Markus Schirp 2014-07-06 14:30:28 +00:00
parent 09edb2573e
commit dbafc31462
7 changed files with 143 additions and 74 deletions

View file

@ -1,3 +1,3 @@
---
threshold: 18
total_score: 1045
total_score: 1053

View file

@ -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

View file

@ -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

View file

@ -12,23 +12,51 @@ module Mutant
# @api private
#
def result
matchers = config.match_expressions.map(&method(:matcher))
if matchers.empty?
return Matcher::Null.new
Filter.new(
Chain.build(config.match_expressions.map(&method(:matcher))),
predicate
)
end
matcher = Matcher::Chain.build(matchers)
# Subject expression prefix predicate
class SubjectPrefix
include Concord.new(:expression)
if predicate
Matcher::Filter.new(matcher, predicate)
else
matcher
end
# 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

View file

@ -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
#

View file

@ -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

View 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