Cleanup matcher and classifier interactions

* Some smells remain but it becomes cleaner now
This commit is contained in:
Markus Schirp 2013-01-21 22:56:52 +01:00
parent a3fc233d95
commit eeb82ee8c6
14 changed files with 129 additions and 230 deletions

View file

@ -23,6 +23,7 @@ module Mutant
# @api private
#
def matcher
# TODO: Honor law of demeter
scope_matcher.matcher.new(scope, method)
end
memoize :matcher
@ -36,6 +37,7 @@ module Mutant
# @api private
#
def method
# TODO: Honor law of demeter
scope_matcher.methods.detect do |method|
method.name == method_name
end || raise("Cannot find #{method_name} for #{scope}")

View file

@ -4,6 +4,25 @@ module Mutant
include Adamantium::Flat, Enumerable, AbstractType
extend DescendantsTracker
# Enumerate subjects
#
# @param [Object] input
#
# @return [self]
# if block given
#
# @return [Enumerator<Subject>]
#
# @api private
#
def self.each(input, &block)
return to_enum(__method__, input) unless block_given?
new(input).each(&block)
self
end
# Enumerate subjects
#
# @api private

View file

@ -1,6 +1,6 @@
module Mutant
class Matcher
class Method < self
class Method
# Matcher for instance methods
class Instance < self
SUBJECT_CLASS = Subject::Method::Instance
@ -14,6 +14,7 @@ module Mutant
def identification
"#{scope.name}##{method_name}"
end
memoize :identification
private
@ -30,7 +31,7 @@ module Mutant
# @api private
#
def match?(node)
node.line == source_line &&
node.line == source_line &&
node.class == Rubinius::AST::Define &&
node.name == method_name
end

View file

@ -2,16 +2,28 @@ module Mutant
class Matcher
# Abstract base class for matcher that returns method subjects extracted from scope
class Methods < self
include AbstractType
include AbstractType, Equalizer.new(:scope)
# Return scope
#
# @return [Class,Model]
# @return [Class, Model]
#
# @api private
#
attr_reader :scope
# Initialize object
#
# @param [Class,Module] scope
#
# @return [undefined]
#
# @api private
#
def initialize(scope)
@scope = scope
end
# Enumerate subjects
#
# @return [self]
@ -32,6 +44,16 @@ module Mutant
self
end
# Return method matcher class
#
# @return [Class:Matcher::Method]
#
# @api private
#
def matcher
self.class::MATCHER
end
# Return methods
#
# @return [Enumerable<Method, UnboundMethod>]
@ -45,15 +67,7 @@ module Mutant
end
memoize :methods
# Return method matcher class
#
# @return [Class:Matcher::Method]
#
# @api private
#
def matcher
self.class::MATCHER
end
private
# Return method names
#
@ -69,20 +83,6 @@ module Mutant
object.protected_instance_methods(false)
end
private
# Initialize object
#
# @param [Class,Module] scope
#
# @return [undefined]
#
# @api private
#
def initialize(scope)
@scope = scope
end
# Emit matches for method
#
# @param [UnboundMethod, Method] method

View file

@ -55,7 +55,7 @@ module Mutant
# @api private
#
def pattern
%r(\A#{Regexp.escape(namespace_name)}(?:::)?\z)
%r(\A#{Regexp.escape(namespace.name)}(?:::)?\z)
end
memoize :pattern
@ -69,7 +69,7 @@ module Mutant
#
def emit_scope_matches(scope, &block)
MATCHERS.each do |matcher|
matcher.new(scope).each(&block)
matcher.each(scope, &block)
end
end
@ -82,7 +82,7 @@ module Mutant
def scopes(&block)
return to_enum(__method__) unless block_given?
::ObjectSpace.each_object(Module) do |scope|
::ObjectSpace.each_object(Module).each do |scope|
emit_scope(scope, &block)
end
end
@ -96,7 +96,7 @@ module Mutant
# @api private
#
def emit_scope(scope)
if [::Module, ::Class].include?(scope.class) and pattern =~ scope.name
if pattern =~ scope.name
yield scope
end
end

View file

@ -60,26 +60,23 @@ describe Mutant::CLI, '.new' do
end
context 'with code filter and missing argument' do
let(:arguments) { %w(--rspec-unit --code) }
let(:arguments) { %w(--rspec-unit --code) }
let(:expected_message) { '"--code" is missing an argument' }
it_should_behave_like 'an invalid cli run'
end
context 'with explicit method matcher' do
let(:arguments) { %w(--rspec-unit TestApp::Literal#float) }
let(:expected_matcher) { Mutant::Matcher::Method.parse('TestApp::Literal#float') }
let(:arguments) { %w(--rspec-unit TestApp::Literal#float) }
let(:expected_matcher) { Mutant::CLI::Classifier::Method.new('TestApp::Literal#float') }
it_should_behave_like 'a cli parser'
end
context 'with namespace matcher' do
let(:arguments) { %w(--rspec-unit ::TestApp) }
let(:expected_matcher) { Mutant::Matcher::ObjectSpace.new(%r(\ATestApp(\z|::))) }
let(:arguments) { %w(--rspec-unit ::TestApp*) }
let(:expected_matcher) { Mutant::CLI::Classifier::Namespace.new('::TestApp*') }
it_should_behave_like 'a cli parser'
end
@ -94,8 +91,8 @@ describe Mutant::CLI, '.new' do
]
end
let(:expected_matcher) { Mutant::Matcher::Method.parse('TestApp::Literal#float') }
let(:expected_filter) { Mutant::Mutation::Filter::Whitelist.new(filters) }
let(:expected_matcher) { Mutant::CLI::Classifier::Method.new('TestApp::Literal#float') }
let(:expected_filter) { Mutant::Mutation::Filter::Whitelist.new(filters) }
it_should_behave_like 'a cli parser'
end

View file

@ -0,0 +1,44 @@
require 'spec_helper'
describe Mutant::CLI::Classifier, '.build' do
subject { described_class.build(input) }
this_spec = 'Mutant::CLI::Classifier.build'
shared_examples_for this_spec do
it 'shoud return expected instance' do
should eql(expected_class.new(expected_class::REGEXP.match(input)))
end
end
context 'with explicit toplevel scope' do
let(:input) { '::TestApp::Literal#string' }
let(:expected_class) { Mutant::CLI::Classifier::Method }
it_should_behave_like this_spec
end
context 'with instance method notation' do
let(:input) { 'TestApp::Literal#string' }
let(:expected_class) { Mutant::CLI::Classifier::Method }
it_should_behave_like this_spec
end
context 'with singleton method notation' do
let(:input) { 'TestApp::Literal.string' }
let(:expected_class) { Mutant::CLI::Classifier::Method }
it_should_behave_like this_spec
end
context 'with invalid notation' do
let(:input) { '::' }
it 'should return nil' do
should be(nil)
end
end
end

View file

@ -1,49 +0,0 @@
require 'spec_helper'
describe Mutant::Matcher, '.from_string' do
subject { object.from_string(input) }
let(:input) { mock('Input') }
let(:matcher) { mock('Matcher') }
let(:descendant_a) { mock('Descendant A', :parse => nil) }
let(:descendant_b) { mock('Descendant B', :parse => nil) }
let(:object) { described_class }
before do
described_class.stub(:descendants => [descendant_a, descendant_b])
end
context 'when no descendant takes the input' do
it { should be(nil) }
it_should_behave_like 'an idempotent method'
end
context 'when one descendant handles input' do
before do
descendant_a.stub(:parse => matcher)
end
it { should be(matcher) }
it_should_behave_like 'an idempotent method'
end
context 'when more than one descendant handles input' do
let(:matcher_b) { mock('Matcher B') }
before do
descendant_a.stub(:parse => matcher)
descendant_b.stub(:parse => matcher_b)
end
it 'should return the first matcher' do
should be(matcher)
end
it_should_behave_like 'an idempotent method'
end
end

View file

@ -1,12 +0,0 @@
require 'spec_helper'
describe Mutant::Matcher, '.parse' do
subject { object.parse(input) }
let(:input) { mock('Input') }
let(:object) { described_class }
it { should be(nil) }
it_should_behave_like 'an idempotent method'
end

View file

@ -1,21 +0,0 @@
require 'spec_helper'
describe Mutant::Matcher::Method, '.parse' do
subject { described_class.parse(input) }
let(:response) { mock('Response') }
let(:input) { mock('Input') }
let(:classifier) { described_class::Classifier }
before do
classifier.stub(:run => response)
end
it { should be(response) }
it 'should call classifier' do
classifier.should_receive(:run).with(input).and_return(response)
subject
end
end

View file

@ -1,52 +0,0 @@
require 'spec_helper'
describe Mutant::Matcher::Method::Classifier, '.run' do
subject { described_class.run(input) }
shared_examples_for 'Mutant::Matcher::Method::Classifier.run' do
before do
expected_class.stub(:new => response)
end
let(:response) { :Response }
it { should be(response) }
it 'should initialize method filter with correct arguments' do
expected_class.should_receive(:new).with(TestApp::Literal, expected_method).and_return(response)
subject
end
end
context 'with explicit toplevel scope' do
let(:input) { '::TestApp::Literal#string' }
let(:expected_class) { Mutant::Matcher::Method::Instance }
let(:expected_method) { TestApp::Literal.instance_method(:string) }
it_should_behave_like 'Mutant::Matcher::Method::Classifier.run'
end
context 'with instance method notation' do
let(:input) { 'TestApp::Literal#string' }
let(:expected_method) { TestApp::Literal.instance_method(:string) }
let(:expected_class) { Mutant::Matcher::Method::Instance }
it_should_behave_like 'Mutant::Matcher::Method::Classifier.run'
end
context 'with singleton method notation' do
let(:input) { 'TestApp::Literal.string' }
let(:expected_method) { TestApp::Literal.method(:string) }
let(:expected_class) { Mutant::Matcher::Method::Singleton }
it_should_behave_like 'Mutant::Matcher::Method::Classifier.run'
end
context 'with invalid notation' do
let(:input) { 'Foo' }
it 'should return nil' do
should be(nil)
end
end
end

View file

@ -0,0 +1,25 @@
require 'spec_helper'
describe Mutant::Matcher::Namespace, '#each' do
subject { object.each { |item| yields << item } }
let(:yields) { [] }
let(:object) { described_class.new(TestApp::Literal) }
let(:singleton_a) { mock('SingletonA', :name => 'TestApp::Literal') }
let(:singleton_b) { mock('SingletonB', :name => 'TestApp::Foo') }
let(:subject_a) { mock('SubjectA') }
let(:subject_b) { mock('SubjectB') }
before do
Mutant::Matcher::Methods::Singleton.stub(:each).with(singleton_a).and_yield(subject_a)
Mutant::Matcher::Methods::Instance.stub(:each).with(singleton_a).and_yield(subject_b)
ObjectSpace.stub(:each_object => [singleton_a, singleton_b])
end
it_should_behave_like 'an #each method'
it 'should yield subjects' do
expect { subject }.to change { yields }.from([]).to([subject_a, subject_b])
end
end

View file

@ -1,24 +0,0 @@
require 'spec_helper'
describe Mutant::Matcher::ObjectSpace, '.parse' do
subject { object.parse(input) }
let(:object) { described_class }
let(:matcher) { mock('Matcher') }
context 'with valid notation' do
let(:input) { '::TestApp::Literal' }
it 'should return matcher' do
described_class.should_receive(:new).with(%r(\ATestApp::Literal(\z|::))).and_return(matcher)
should be(matcher)
end
end
context 'with invalid notation' do
let(:input) { 'TestApp' }
it { should be(nil) }
end
end

View file

@ -1,31 +0,0 @@
require 'spec_helper'
describe Mutant::Matcher::ObjectSpace, '#each' do
before do
pending "defunct"
end
subject { object.each { |item| yields << item } }
let(:yields) { [] }
let(:object) { described_class.new(/\ATestApp::Literal(\z|::)/) }
before do
Mutant::Matcher::Method::Singleton.stub(:each).and_yield(matcher_a)
Mutant::Matcher::Method::Instance.stub(:each).and_yield(matcher_b)
matcher_a.stub(:each).and_yield(subject_a)
matcher_b.stub(:each).and_yield(subject_b)
end
let(:matcher_a) { mock('Matcher A') }
let(:matcher_b) { mock('Matcher B') }
let(:subject_a) { mock('Subject A') }
let(:subject_b) { mock('Subject B') }
it_should_behave_like 'an #each method'
it 'should yield subjects' do
expect { subject }.to change { yields }.from([]).to([subject_a, subject_b])
end
end