Cleanup matcher and classifier interactions
* Some smells remain but it becomes cleaner now
This commit is contained in:
parent
a3fc233d95
commit
eeb82ee8c6
14 changed files with 129 additions and 230 deletions
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
44
spec/unit/mutant/cli/classifier/class_methods/build_spec.rb
Normal file
44
spec/unit/mutant/cli/classifier/class_methods/build_spec.rb
Normal 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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
25
spec/unit/mutant/matcher/namespace/each_spec.rb
Normal file
25
spec/unit/mutant/matcher/namespace/each_spec.rb
Normal 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
|
|
@ -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
|
|
@ -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
|
Loading…
Add table
Reference in a new issue