Reorganize classifiers
* Classifiers are matcher subclass and delegate to lazy builded matcher interfaces * Solves chicken egg problem between target library load and classifier matcher instantiation
This commit is contained in:
parent
b024508ae9
commit
a3fc233d95
10 changed files with 242 additions and 155 deletions
|
@ -89,12 +89,11 @@ require 'mutant/subject'
|
|||
require 'mutant/subject/method'
|
||||
require 'mutant/matcher'
|
||||
require 'mutant/matcher/chain'
|
||||
require 'mutant/matcher/object_space'
|
||||
require 'mutant/matcher/method'
|
||||
require 'mutant/matcher/method/singleton'
|
||||
require 'mutant/matcher/method/instance'
|
||||
require 'mutant/matcher/scope_methods'
|
||||
require 'mutant/matcher/method/classifier'
|
||||
require 'mutant/matcher/methods'
|
||||
require 'mutant/matcher/namespace'
|
||||
require 'mutant/killer'
|
||||
require 'mutant/killer/static'
|
||||
require 'mutant/killer/rspec'
|
||||
|
@ -109,6 +108,9 @@ require 'mutant/strategy/rspec/dm2/lookup'
|
|||
require 'mutant/strategy/rspec/dm2/lookup/method'
|
||||
require 'mutant/runner'
|
||||
require 'mutant/cli'
|
||||
require 'mutant/cli/classifier'
|
||||
require 'mutant/cli/classifier/namespace'
|
||||
require 'mutant/cli/classifier/method'
|
||||
require 'mutant/color'
|
||||
require 'mutant/differ'
|
||||
require 'mutant/reporter'
|
||||
|
|
|
@ -9,6 +9,19 @@ module Mutant
|
|||
EXIT_FAILURE = 1
|
||||
EXIT_SUCCESS = 0
|
||||
|
||||
OPTIONS = {
|
||||
'--code' => [:add_filter, Mutation::Filter::Code ],
|
||||
'--debug' => [:set_debug ],
|
||||
'-d' => [:set_debug ],
|
||||
'--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ],
|
||||
'--rspec-full' => [:set_strategy, Strategy::Rspec::Full ],
|
||||
'--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ],
|
||||
'--static-fail' => [:set_strategy, Strategy::Static::Fail ],
|
||||
'--static-success' => [:set_strategy, Strategy::Static::Success ]
|
||||
}.freeze
|
||||
|
||||
OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze
|
||||
|
||||
# Run cli with arguments
|
||||
#
|
||||
# @param [Array<String>] arguments
|
||||
|
@ -98,19 +111,6 @@ module Mutant
|
|||
|
||||
private
|
||||
|
||||
OPTIONS = {
|
||||
'--code' => [:add_filter, Mutation::Filter::Code ],
|
||||
'--debug' => [:set_debug ],
|
||||
'-d' => [:set_debug ],
|
||||
'--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ],
|
||||
'--rspec-full' => [:set_strategy, Strategy::Rspec::Full ],
|
||||
'--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ],
|
||||
'--static-fail' => [:set_strategy, Strategy::Static::Fail ],
|
||||
'--static-success' => [:set_strategy, Strategy::Static::Success ]
|
||||
}.freeze
|
||||
|
||||
OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze
|
||||
|
||||
# Initialize CLI
|
||||
#
|
||||
# @param [Array<String>] arguments
|
||||
|
@ -204,15 +204,8 @@ module Mutant
|
|||
#
|
||||
def dispatch_matcher
|
||||
argument = current_argument
|
||||
matcher = Mutant::Matcher.from_string(argument)
|
||||
|
||||
unless matcher
|
||||
raise Error, "Invalid matcher syntax: #{argument.inspect}"
|
||||
end
|
||||
|
||||
@matchers << matcher
|
||||
|
||||
consume(1)
|
||||
@matchers << Classifier.build(argument)
|
||||
end
|
||||
|
||||
# Process option argument
|
||||
|
|
129
lib/mutant/cli/classifier.rb
Normal file
129
lib/mutant/cli/classifier.rb
Normal file
|
@ -0,0 +1,129 @@
|
|||
module Mutant
|
||||
class CLI
|
||||
# A classifier for input strings
|
||||
class Classifier < Matcher
|
||||
include AbstractType, Adamantium::Flat, Equalizer.new(:identification)
|
||||
extend DescendantsTracker
|
||||
|
||||
SCOPE_NAME_PATTERN = /[A-Za-z][A-Za-z_0-9]*/.freeze
|
||||
METHOD_NAME_PATTERN = /[_A-Za-z][A-Za-z0-9_]*[!?=]?/.freeze
|
||||
SCOPE_PATTERN = /(?:::)?#{SCOPE_NAME_PATTERN}(?:::#{SCOPE_NAME_PATTERN})*/.freeze
|
||||
|
||||
SINGLETON_PATTERN = %r(\A(#{SCOPE_PATTERN})\z).freeze
|
||||
|
||||
# Return constant
|
||||
#
|
||||
# @param [String] location
|
||||
#
|
||||
# @return [Class|Module]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.constant_lookup(location)
|
||||
location.gsub(%r(\A::), '').split('::').inject(::Object) do |parent, name|
|
||||
parent.const_get(name)
|
||||
end
|
||||
end
|
||||
|
||||
# Return matchers for input
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Classifier]
|
||||
# if a classifier handles the input
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(input)
|
||||
classifiers = descendants.map do |descendant|
|
||||
descendant.run(input)
|
||||
end.compact
|
||||
|
||||
raise if classifiers.length > 1
|
||||
|
||||
classifiers.first
|
||||
end
|
||||
|
||||
# Run classifier
|
||||
#
|
||||
# @return [Classifier]
|
||||
# if input is handled by classifier
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(input)
|
||||
match = self::REGEXP.match(input)
|
||||
return unless match
|
||||
|
||||
new(match)
|
||||
end
|
||||
|
||||
# No protected_class_method in ruby :(
|
||||
class << self; protected :run; end
|
||||
|
||||
# Enumerate subjects
|
||||
#
|
||||
# @return [self]
|
||||
# if block given
|
||||
#
|
||||
# @return [Enumerator<Subject>]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def each(&block)
|
||||
return to_enum unless block_given?
|
||||
matcher.each(&block)
|
||||
self
|
||||
end
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
match.to_s
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
private
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
# @param [MatchData] match
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(match)
|
||||
@match = match
|
||||
end
|
||||
|
||||
# Return match
|
||||
#
|
||||
# @return [MatchData]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :match
|
||||
|
||||
# Return matcher
|
||||
#
|
||||
# @return [Matcher]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :matcher
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,41 +1,20 @@
|
|||
module Mutant
|
||||
class Matcher
|
||||
class Method < self
|
||||
# A classifier for input strings
|
||||
class Classifier
|
||||
include Adamantium::Flat
|
||||
class CLI
|
||||
class Classifier
|
||||
# Explicit method classifier
|
||||
class Method < self
|
||||
|
||||
TABLE = {
|
||||
'.' => Matcher::ScopeMethods::Singleton,
|
||||
'#' => Matcher::ScopeMethods::Instance
|
||||
'.' => Matcher::Methods::Singleton,
|
||||
'#' => Matcher::Methods::Instance
|
||||
}.freeze
|
||||
|
||||
SCOPE_FORMAT = /\A([^#.]+)(\.|#)(.+)\z/.freeze
|
||||
REGEXP = %r(\A(#{SCOPE_PATTERN})([.#])(#{METHOD_NAME_PATTERN}\z)).freeze
|
||||
|
||||
# Positions of captured regexp groups
|
||||
SCOPE_NAME_POSITION = 1
|
||||
SCOPE_SYMBOL_POSITION = 2
|
||||
METHOD_NAME_POSITION = 3
|
||||
|
||||
private_class_method :new
|
||||
|
||||
# Run classifier
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Matcher::Method]
|
||||
# returns matcher when input is in
|
||||
#
|
||||
# @return [nil]
|
||||
# returns nil otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(input)
|
||||
match = SCOPE_FORMAT.match(input)
|
||||
return unless match
|
||||
new(match).matcher
|
||||
end
|
||||
SCOPE_NAME_POSITION = 1
|
||||
SCOPE_SYMBOL_POSITION = 2
|
||||
METHOD_NAME_POSITION = 3
|
||||
|
||||
# Return method matcher
|
||||
#
|
||||
|
@ -48,6 +27,8 @@ module Mutant
|
|||
end
|
||||
memoize :matcher
|
||||
|
||||
private
|
||||
|
||||
# Return method
|
||||
#
|
||||
# @return [Method, UnboundMethod]
|
||||
|
@ -61,39 +42,6 @@ module Mutant
|
|||
end
|
||||
memoize :method, :freezer => :noop
|
||||
|
||||
# Return match
|
||||
#
|
||||
# @return [Matche]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :match
|
||||
|
||||
private
|
||||
|
||||
# Initialize matcher
|
||||
#
|
||||
# @param [MatchData] match
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(match)
|
||||
@match = match
|
||||
end
|
||||
|
||||
# Return scope
|
||||
#
|
||||
# @return [Class|Module]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scope
|
||||
scope_name.gsub(%r(\A::), '').split('::').inject(::Object) do |parent, name|
|
||||
parent.const_get(name)
|
||||
end
|
||||
end
|
||||
memoize :scope
|
||||
|
||||
# Return scope name
|
||||
#
|
||||
# @return [String]
|
||||
|
@ -104,6 +52,16 @@ module Mutant
|
|||
match[SCOPE_NAME_POSITION]
|
||||
end
|
||||
|
||||
# Return scope
|
||||
#
|
||||
# @return [Class, Method]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scope
|
||||
Classifier.constant_lookup(scope_name)
|
||||
end
|
||||
|
||||
# Return method name
|
||||
#
|
||||
# @return [String]
|
||||
|
@ -134,6 +92,7 @@ module Mutant
|
|||
TABLE.fetch(scope_symbol).new(scope)
|
||||
end
|
||||
memoize :scope_matcher
|
||||
|
||||
end
|
||||
end
|
||||
end
|
34
lib/mutant/cli/classifier/namespace.rb
Normal file
34
lib/mutant/cli/classifier/namespace.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module Mutant
|
||||
class CLI
|
||||
class Classifier
|
||||
|
||||
# Namespace classifier
|
||||
class Namespace < self
|
||||
|
||||
REGEXP = %r(\A(#{SCOPE_PATTERN})\*\z).freeze
|
||||
|
||||
private
|
||||
|
||||
# Return matcher
|
||||
#
|
||||
# @return [Matcher]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matcher
|
||||
Matcher::Namespace.new(namespace)
|
||||
end
|
||||
|
||||
# Return namespace
|
||||
#
|
||||
# @return [Class, Module]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def namespace
|
||||
Classifier.const_lookup(match.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
module Mutant
|
||||
# Abstract matcher to find ASTs to mutate
|
||||
# Abstract matcher to find subjects to mutate
|
||||
class Matcher
|
||||
include Adamantium::Flat, Enumerable, AbstractType
|
||||
extend DescendantsTracker
|
||||
|
@ -8,7 +8,11 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
# @return [undefined]
|
||||
# @return [self]
|
||||
# if block given
|
||||
#
|
||||
# @return [Enumerabe<Subject>]
|
||||
# otherwise
|
||||
#
|
||||
abstract_method :each
|
||||
|
||||
|
@ -19,39 +23,5 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
abstract_method :identification
|
||||
|
||||
# Return matcher
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [nil]
|
||||
# returns nil as default implementation
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.parse(input)
|
||||
nil
|
||||
end
|
||||
|
||||
# Return match from string
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Matcher]
|
||||
# returns matcher input if successful
|
||||
#
|
||||
# @return [nil]
|
||||
# returns nil otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.from_string(input)
|
||||
descendants.each do |descendant|
|
||||
matcher = descendant.parse(input)
|
||||
return matcher if matcher
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,18 +4,6 @@ module Mutant
|
|||
class Method < self
|
||||
include Adamantium::Flat, Equalizer.new(:identification)
|
||||
|
||||
# Parse a method string into filter
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Matcher::Method]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.parse(input)
|
||||
Classifier.run(input)
|
||||
end
|
||||
|
||||
# Methods within rbx kernel directory are precompiled and their source
|
||||
# cannot be accessed via reading source location
|
||||
BLACKLIST = %r(\Akernel/).freeze
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Mutant
|
||||
class Matcher
|
||||
# Abstract base class for matcher that returns subjects extracted from scope methods
|
||||
class ScopeMethods < self
|
||||
# Abstract base class for matcher that returns method subjects extracted from scope
|
||||
class Methods < self
|
||||
include AbstractType
|
||||
|
||||
# Return scope
|
|
@ -1,16 +1,16 @@
|
|||
module Mutant
|
||||
class Matcher
|
||||
# Matcher against object space
|
||||
class ObjectSpace < self
|
||||
include Equalizer.new(:scope_name_pattern)
|
||||
# Matcher for specific namespace
|
||||
class Namespace < self
|
||||
include Equalizer.new(:pattern)
|
||||
|
||||
# Enumerate subjects
|
||||
#
|
||||
# @return [Enumerator<Subject>]
|
||||
# returns subject enumerator when no block given
|
||||
#
|
||||
# @return [self]
|
||||
# returns self otherwise
|
||||
# if block given
|
||||
#
|
||||
# @return [Enumerator<Subject>]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
|
@ -24,29 +24,41 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Return scope name pattern
|
||||
# Return namespace
|
||||
#
|
||||
# @return [Regexp]
|
||||
# @return [Class::Module]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :scope_name_pattern
|
||||
attr_reader :namespace
|
||||
|
||||
MATCHERS = [Matcher::Methods::Singleton, Matcher::Methods::Instance]
|
||||
|
||||
private
|
||||
|
||||
# Initialize object space matcher
|
||||
#
|
||||
# @param [Regexp] scope_name_pattern
|
||||
# @param [Enumerable<#each(scope)>] matchers
|
||||
# @param [Class, Module] namespace
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(scope_name_pattern, matchers = [Matcher::ScopeMethods::Singleton, Matcher::ScopeMethods::Instance])
|
||||
@scope_name_pattern, @matchers = scope_name_pattern, @matchers = matchers #[Method::Singleton, Method::Instance]
|
||||
def initialize(namespace)
|
||||
@namespace = namespace
|
||||
end
|
||||
|
||||
# Return pattern
|
||||
#
|
||||
# @return [Regexp]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def pattern
|
||||
%r(\A#{Regexp.escape(namespace_name)}(?:::)?\z)
|
||||
end
|
||||
memoize :pattern
|
||||
|
||||
# Yield matchers for scope
|
||||
#
|
||||
# @param [::Class,::Module] scope
|
||||
|
@ -56,7 +68,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def emit_scope_matches(scope, &block)
|
||||
@matchers.each do |matcher|
|
||||
MATCHERS.each do |matcher|
|
||||
matcher.new(scope).each(&block)
|
||||
end
|
||||
end
|
||||
|
@ -84,7 +96,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def emit_scope(scope)
|
||||
if [::Module, ::Class].include?(scope.class) and scope_name_pattern =~ scope.name
|
||||
if [::Module, ::Class].include?(scope.class) and pattern =~ scope.name
|
||||
yield scope
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@ describe Mutant, 'method matching' do
|
|||
this_example = 'Mutant method matching'
|
||||
|
||||
shared_examples_for this_example do
|
||||
subject { Mutant::Matcher::Method.parse(pattern).to_a }
|
||||
subject { Mutant::CLI::Classifier.build(pattern).to_a }
|
||||
|
||||
let(:values) { defaults.merge(expectation) }
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue