Refactor expression parsing and representation
* Avoids boot time mutation of REGISTER constant * Allows to define project specific expression parsing * Avoids custom (slow) serialization of Expression objects speeding up reporter / killer IPC * Improve specification * Improve integration API as it now finally references an object the config * Allow reproduction of syntax from Expression#syntax * Allow instantiation of Expresssion objects without generating the syntax, much nicer for most specs & internal code, avoids generating a string to parse it into an expression * Fix LSP violation in Mutant::Matcher namespace
This commit is contained in:
parent
c7113f7e82
commit
d647563055
41 changed files with 467 additions and 464 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 1194
|
||||
total_score: 1185
|
||||
|
|
|
@ -143,6 +143,7 @@ require 'mutant/matcher/scope'
|
|||
require 'mutant/matcher/filter'
|
||||
require 'mutant/matcher/null'
|
||||
require 'mutant/expression'
|
||||
require 'mutant/expression/parser'
|
||||
require 'mutant/expression/method'
|
||||
require 'mutant/expression/methods'
|
||||
require 'mutant/expression/namespace'
|
||||
|
@ -192,7 +193,13 @@ module Mutant
|
|||
reporter: Reporter::CLI.build($stdout),
|
||||
zombie: false,
|
||||
jobs: Mutant.ci? ? CI_DEFAULT_PROCESSOR_COUNT : ::Parallel.processor_count,
|
||||
expected_coverage: Rational(1)
|
||||
expected_coverage: Rational(1),
|
||||
expression_parser: Expression::Parser.new([
|
||||
Expression::Method,
|
||||
Expression::Methods,
|
||||
Expression::Namespace::Exact,
|
||||
Expression::Namespace::Recursive
|
||||
])
|
||||
)
|
||||
end # Config
|
||||
end # Mutant
|
||||
|
|
|
@ -86,8 +86,8 @@ module Mutant
|
|||
def parse_match_expressions(expressions)
|
||||
fail Error, 'No expressions given' if expressions.empty?
|
||||
|
||||
expressions.map(&Expression.method(:parse)).each do |expression|
|
||||
add_matcher(:match_expressions, expression)
|
||||
expressions.each do |expression|
|
||||
add_matcher(:match_expressions, config.expression_parser.(expression))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -162,7 +162,7 @@ module Mutant
|
|||
#
|
||||
def add_filter_options(opts)
|
||||
opts.on('--ignore-subject PATTERN', 'Ignore subjects that match PATTERN') do |pattern|
|
||||
add_matcher(:subject_ignores, Expression.parse(pattern))
|
||||
add_matcher(:subject_ignores, config.expression_parser.(pattern))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ module Mutant
|
|||
:fail_fast,
|
||||
:jobs,
|
||||
:zombie,
|
||||
:expected_coverage
|
||||
:expected_coverage,
|
||||
:expression_parser
|
||||
)
|
||||
|
||||
%i[fail_fast zombie debug].each do |name|
|
||||
|
|
|
@ -87,7 +87,9 @@ module Mutant
|
|||
#
|
||||
def match_expressions
|
||||
name_nesting.each_index.reverse_each.map do |index|
|
||||
Expression.parse("#{name_nesting.take(index.succ).join(NAMESPACE_DELIMITER)}*")
|
||||
Expression::Namespace::Recursive.new(
|
||||
scope_name: name_nesting.take(index.succ).join(NAMESPACE_DELIMITER)
|
||||
)
|
||||
end
|
||||
end
|
||||
memoize :match_expressions
|
||||
|
|
50
lib/mutant/env/bootstrap.rb
vendored
50
lib/mutant/env/bootstrap.rb
vendored
|
@ -106,6 +106,31 @@ module Mutant
|
|||
@integration = config.integration.new(config).setup
|
||||
end
|
||||
|
||||
# Return matched subjects
|
||||
#
|
||||
# @return [Enumerable<Subject>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matched_subjects
|
||||
Matcher::Compiler.call(self, config.matcher).to_a
|
||||
end
|
||||
|
||||
# Initialize matchable scopes
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize_matchable_scopes
|
||||
scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
|
||||
expression = expression(scope)
|
||||
aggregate << Matcher::Scope.new(self, scope, expression) if expression
|
||||
end
|
||||
|
||||
@matchable_scopes = scopes.sort_by { |scope| scope.expression.syntax }
|
||||
end
|
||||
|
||||
# Try to turn scope into expression
|
||||
#
|
||||
# @param [Class, Module] scope
|
||||
|
@ -126,30 +151,7 @@ module Mutant
|
|||
return
|
||||
end
|
||||
|
||||
Expression.try_parse(name)
|
||||
end
|
||||
|
||||
# Return matched subjects
|
||||
#
|
||||
# @return [Enumerable<Subject>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matched_subjects
|
||||
Matcher::Compiler.call(self, config.matcher).to_a
|
||||
end
|
||||
|
||||
# Initialize matchable scopes
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize_matchable_scopes
|
||||
@matchable_scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
|
||||
expression = expression(scope)
|
||||
aggregate << Matcher::Scope.new(self, scope, expression) if expression
|
||||
end.sort_by(&:identification)
|
||||
config.expression_parser.try_parse(name)
|
||||
end
|
||||
end # Boostrap
|
||||
end # Env
|
||||
|
|
|
@ -2,83 +2,21 @@ module Mutant
|
|||
|
||||
# Abstract base class for match expression
|
||||
class Expression
|
||||
include AbstractType, Adamantium::Flat, Concord::Public.new(:match)
|
||||
include AbstractType, Adamantium::Flat
|
||||
|
||||
include Equalizer.new(:syntax)
|
||||
fragment = /[A-Za-z][A-Za-z\d_]*/.freeze
|
||||
SCOPE_NAME_PATTERN = /(?<scope_name>#{fragment}(?:#{SCOPE_OPERATOR}#{fragment})*)/.freeze
|
||||
SCOPE_SYMBOL_PATTERN = '(?<scope_symbol>[.#])'.freeze
|
||||
|
||||
SCOPE_NAME_PATTERN = /[A-Za-z][A-Za-z\d_]*/.freeze
|
||||
private_constant(*constants(false))
|
||||
|
||||
METHOD_NAME_PATTERN = Regexp.union(
|
||||
/[A-Za-z_][A-Za-z\d_]*[!?=]?/,
|
||||
*AST::Types::OPERATOR_METHODS.map(&:to_s)
|
||||
).freeze
|
||||
|
||||
INSPECT_FORMAT = '<Mutant::Expression: %s>'.freeze
|
||||
|
||||
SCOPE_PATTERN = /#{SCOPE_NAME_PATTERN}(?:#{SCOPE_OPERATOR}#{SCOPE_NAME_PATTERN})*/.freeze
|
||||
|
||||
REGISTRY = {}
|
||||
|
||||
# Error raised on invalid expressions
|
||||
class InvalidExpressionError < RuntimeError; end
|
||||
|
||||
# Error raised on ambiguous expressions
|
||||
class AmbiguousExpressionError < RuntimeError; end
|
||||
|
||||
# Initialize expression
|
||||
#
|
||||
# @param [MatchData] match
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(*)
|
||||
super
|
||||
@syntax = match.to_s
|
||||
@inspect = format(INSPECT_FORMAT, syntax)
|
||||
end
|
||||
|
||||
# Return marshallable representation
|
||||
#
|
||||
# FIXME: Remove the need for this.
|
||||
#
|
||||
# Refactoring Expression objects not to reference a MatchData instance.
|
||||
# This will make this hack unneeded.
|
||||
# Return syntax representing this expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def _dump(_level)
|
||||
syntax
|
||||
end
|
||||
|
||||
# Load serializable representation
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @return [Expression]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self._load(syntax)
|
||||
parse(syntax)
|
||||
end
|
||||
|
||||
# Return inspection
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :inspect
|
||||
|
||||
# Return syntax
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :syntax
|
||||
abstract_method :syntax
|
||||
|
||||
# Return match length for expression
|
||||
#
|
||||
|
@ -108,76 +46,23 @@ module Mutant
|
|||
!match_length(other).zero?
|
||||
end
|
||||
|
||||
# Register expression
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.register(regexp)
|
||||
REGISTRY[regexp] = self
|
||||
end
|
||||
private_class_method :register
|
||||
|
||||
# Parse input into expression or raise
|
||||
#
|
||||
# @param [String] syntax
|
||||
#
|
||||
# @return [Expression]
|
||||
# if expression is valid
|
||||
#
|
||||
# @raise [RuntimeError]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.parse(input)
|
||||
try_parse(input) or fail InvalidExpressionError, "Expression: #{input.inspect} is not valid"
|
||||
end
|
||||
|
||||
# Parse input into expression
|
||||
# Try to parse input into expression of receiver class
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Expression]
|
||||
# if expression is valid
|
||||
# when successful
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.try_parse(input)
|
||||
expressions = expressions(input)
|
||||
case expressions.length
|
||||
when 0
|
||||
when 1
|
||||
expressions.first
|
||||
else
|
||||
fail AmbiguousExpressionError, "Ambiguous expression: #{input.inspect}"
|
||||
end
|
||||
match = self::REGEXP.match(input)
|
||||
return unless match
|
||||
names = anima.attribute_names
|
||||
new(Hash[names.zip(names.map(&match.method(:[])))])
|
||||
end
|
||||
|
||||
# Return expressions for input
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Classifier]
|
||||
# if classifier can be found
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.expressions(input)
|
||||
REGISTRY.each_with_object([]) do |(regexp, klass), expressions|
|
||||
match = regexp.match(input)
|
||||
next unless match
|
||||
expressions << klass.new(match)
|
||||
end
|
||||
end
|
||||
private_class_method :expressions
|
||||
|
||||
end # Expression
|
||||
end # Mutant
|
||||
|
|
|
@ -3,43 +3,49 @@ module Mutant
|
|||
|
||||
# Explicit method expression
|
||||
class Method < self
|
||||
include Anima.new(:scope_name, :scope_symbol, :method_name)
|
||||
private(*anima.attribute_names)
|
||||
|
||||
MATCHERS = IceNine.deep_freeze(
|
||||
'.' => Matcher::Methods::Singleton,
|
||||
'#' => Matcher::Methods::Instance
|
||||
)
|
||||
|
||||
register(
|
||||
/\A(?<scope_name>#{SCOPE_PATTERN})(?<scope_symbol>[.#])(?<method_name>#{METHOD_NAME_PATTERN})\z/
|
||||
)
|
||||
METHOD_NAME_PATTERN = Regexp.union(
|
||||
/(?<method_name>[A-Za-z_][A-Za-z\d_]*[!?=]?)/,
|
||||
*AST::Types::OPERATOR_METHODS.map(&:to_s)
|
||||
).freeze
|
||||
|
||||
# Return method matcher
|
||||
#
|
||||
# @param [Env] env
|
||||
#
|
||||
# @return [Matcher::Method]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matcher(env)
|
||||
methods_matcher = MATCHERS.fetch(scope_symbol).new(env, scope)
|
||||
method = methods_matcher.methods.detect do |meth|
|
||||
meth.name.equal?(method_name)
|
||||
end or fail NameError, "Cannot find method #{method_name}"
|
||||
methods_matcher.matcher.build(env, scope, method)
|
||||
end
|
||||
private_constant(*constants(false))
|
||||
|
||||
private
|
||||
REGEXP = /\A#{SCOPE_NAME_PATTERN}#{SCOPE_SYMBOL_PATTERN}#{METHOD_NAME_PATTERN}\z/.freeze
|
||||
|
||||
# Return scope name
|
||||
# Return syntax
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scope_name
|
||||
match[__method__]
|
||||
def syntax
|
||||
[scope_name, scope_symbol, method_name].join
|
||||
end
|
||||
memoize :syntax
|
||||
|
||||
# Return method matcher
|
||||
#
|
||||
# @param [Env] env
|
||||
#
|
||||
# @return [Matcher]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matcher(env)
|
||||
methods_matcher = MATCHERS.fetch(scope_symbol).new(env, scope)
|
||||
|
||||
Matcher::Filter.build(methods_matcher) { |subject| subject.expression.eql?(self) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return scope
|
||||
#
|
||||
|
@ -51,26 +57,6 @@ module Mutant
|
|||
Object.const_get(scope_name)
|
||||
end
|
||||
|
||||
# Return method name
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def method_name
|
||||
match[__method__].to_sym
|
||||
end
|
||||
|
||||
# Return scope symbol
|
||||
#
|
||||
# @return [Symbol]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scope_symbol
|
||||
match[__method__]
|
||||
end
|
||||
|
||||
end # Method
|
||||
end # Expression
|
||||
end # Mutant
|
||||
|
|
|
@ -3,15 +3,27 @@ module Mutant
|
|||
|
||||
# Abstract base class for methods expression
|
||||
class Methods < self
|
||||
include Anima.new(:scope_name, :scope_symbol)
|
||||
private(*anima.attribute_names)
|
||||
|
||||
MATCHERS = IceNine.deep_freeze(
|
||||
'.' => Matcher::Methods::Singleton,
|
||||
'#' => Matcher::Methods::Instance
|
||||
)
|
||||
private_constant(*constants(false))
|
||||
|
||||
register(
|
||||
/\A(?<scope_name>#{SCOPE_PATTERN})(?<scope_symbol>[.#])\z/
|
||||
)
|
||||
REGEXP = /\A#{SCOPE_NAME_PATTERN}#{SCOPE_SYMBOL_PATTERN}\z/.freeze
|
||||
|
||||
# Return syntax
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def syntax
|
||||
[scope_name, scope_symbol].join
|
||||
end
|
||||
memoize :syntax
|
||||
|
||||
# Return method matcher
|
||||
#
|
||||
|
@ -43,16 +55,6 @@ module Mutant
|
|||
|
||||
private
|
||||
|
||||
# Return scope name
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scope_name
|
||||
match[__method__]
|
||||
end
|
||||
|
||||
# Return scope
|
||||
#
|
||||
# @return [Class, Method]
|
||||
|
@ -63,16 +65,6 @@ module Mutant
|
|||
Object.const_get(scope_name)
|
||||
end
|
||||
|
||||
# Return scope symbol
|
||||
#
|
||||
# @return [Symbol]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scope_symbol
|
||||
match[__method__]
|
||||
end
|
||||
|
||||
end # Method
|
||||
end # Expression
|
||||
end # Mutant
|
||||
|
|
|
@ -2,24 +2,12 @@ module Mutant
|
|||
class Expression
|
||||
# Abstract base class for expressions matching namespaces
|
||||
class Namespace < self
|
||||
include AbstractType
|
||||
|
||||
private
|
||||
|
||||
# Return matched namespace
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def namespace
|
||||
match[__method__]
|
||||
end
|
||||
include AbstractType, Anima.new(:scope_name)
|
||||
private(*anima.attribute_names)
|
||||
|
||||
# Recursive namespace expression
|
||||
class Recursive < self
|
||||
|
||||
register(/\A(?<namespace>#{SCOPE_PATTERN})?\*\z/)
|
||||
REGEXP = /\A#{SCOPE_NAME_PATTERN}?\*\z/.freeze
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
|
@ -29,12 +17,23 @@ module Mutant
|
|||
def initialize(*)
|
||||
super
|
||||
@recursion_pattern = Regexp.union(
|
||||
/\A#{namespace}\z/,
|
||||
/\A#{namespace}::/,
|
||||
/\A#{namespace}[.#]/
|
||||
/\A#{scope_name}\z/,
|
||||
/\A#{scope_name}::/,
|
||||
/\A#{scope_name}[.#]/
|
||||
)
|
||||
end
|
||||
|
||||
# Return the syntax for this expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def syntax
|
||||
"#{scope_name}*"
|
||||
end
|
||||
memoize :syntax
|
||||
|
||||
# Return matcher
|
||||
#
|
||||
# @param [Env::Bootstrap] env
|
||||
|
@ -57,7 +56,7 @@ module Mutant
|
|||
#
|
||||
def match_length(expression)
|
||||
if @recursion_pattern =~ expression.syntax
|
||||
namespace.length
|
||||
scope_name.length
|
||||
else
|
||||
0
|
||||
end
|
||||
|
@ -68,9 +67,10 @@ module Mutant
|
|||
# Exact namespace expression
|
||||
class Exact < self
|
||||
|
||||
register(/\A(?<namespace>#{SCOPE_PATTERN})\z/)
|
||||
|
||||
MATCHER = Matcher::Scope
|
||||
private_constant(*constants(false))
|
||||
|
||||
REGEXP = /\A#{SCOPE_NAME_PATTERN}\z/.freeze
|
||||
|
||||
# Return matcher
|
||||
#
|
||||
|
@ -81,9 +81,18 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def matcher(env)
|
||||
Matcher::Scope.new(env, Object.const_get(namespace), self)
|
||||
Matcher::Scope.new(env, Object.const_get(scope_name), self)
|
||||
end
|
||||
|
||||
# Return the syntax for this expression
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
alias_method :syntax, :scope_name
|
||||
public :syntax
|
||||
|
||||
end # Exact
|
||||
end # Namespace
|
||||
end # Namespace
|
||||
|
|
74
lib/mutant/expression/parser.rb
Normal file
74
lib/mutant/expression/parser.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
module Mutant
|
||||
class Expression
|
||||
class Parser
|
||||
include Concord.new(:types)
|
||||
|
||||
class ParserError < RuntimeError
|
||||
include AbstractType
|
||||
end
|
||||
|
||||
# Error raised on invalid expressions
|
||||
class InvalidExpressionError < ParserError; end
|
||||
|
||||
# Error raised on ambiguous expressions
|
||||
class AmbiguousExpressionError < ParserError; end
|
||||
|
||||
# Parse input into expression or raise
|
||||
#
|
||||
# @param [String] syntax
|
||||
#
|
||||
# @return [Expression]
|
||||
# if expression is valid
|
||||
#
|
||||
# @raise [ParserError]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def call(input)
|
||||
try_parse(input) or fail InvalidExpressionError, "Expression: #{input.inspect} is not valid"
|
||||
end
|
||||
|
||||
# Try to parse input into expression
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Expression]
|
||||
# if expression is valid
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def try_parse(input)
|
||||
expressions = expressions(input)
|
||||
case expressions.length
|
||||
when 0, 1
|
||||
expressions.first
|
||||
else
|
||||
fail AmbiguousExpressionError, "Ambiguous expression: #{input.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return expressions for input
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Array<Expression>]
|
||||
# if expressions can be parsed from input
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def expressions(input)
|
||||
types.each_with_object([]) do |type, aggregate|
|
||||
expression = type.try_parse(input)
|
||||
aggregate << expression if expression
|
||||
end
|
||||
end
|
||||
|
||||
end # Parser
|
||||
end # Expression
|
||||
end # Mutant
|
|
@ -73,6 +73,18 @@ module Mutant
|
|||
#
|
||||
abstract_method :all_tests
|
||||
|
||||
private
|
||||
|
||||
# Return expression parser
|
||||
#
|
||||
# @return [Expression::Parser]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def expression_parser
|
||||
config.expression_parser
|
||||
end
|
||||
|
||||
# Null integration that never kills a mutation
|
||||
class Null < self
|
||||
|
||||
|
|
|
@ -19,12 +19,14 @@ module Mutant
|
|||
# for unique reference.
|
||||
class Rspec < self
|
||||
|
||||
ALL_EXPRESSION = Expression.parse('*').freeze
|
||||
ALL_EXPRESSION = Expression::Namespace::Recursive.new(scope_name: nil)
|
||||
EXPRESSION_CANDIDATE = /\A([^ ]+)(?: )?/.freeze
|
||||
LOCATION_DELIMITER = ':'.freeze
|
||||
EXIT_SUCCESS = 0
|
||||
CLI_OPTIONS = IceNine.deep_freeze(%w[spec --fail-fast])
|
||||
|
||||
private_constant(*constants(false))
|
||||
|
||||
register 'rspec'
|
||||
|
||||
# Initialize rspec integration
|
||||
|
@ -132,10 +134,10 @@ module Mutant
|
|||
#
|
||||
def parse_expression(metadata)
|
||||
if metadata.key?(:mutant_expression)
|
||||
Expression.parse(metadata.fetch(:mutant_expression))
|
||||
expression_parser.(metadata.fetch(:mutant_expression))
|
||||
else
|
||||
match = EXPRESSION_CANDIDATE.match(metadata.fetch(:full_description))
|
||||
Expression.try_parse(match.captures.first) || ALL_EXPRESSION
|
||||
expression_parser.try_parse(match.captures.first) || ALL_EXPRESSION
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -28,13 +28,5 @@ module Mutant
|
|||
#
|
||||
abstract_method :each
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :identification
|
||||
|
||||
end # Matcher
|
||||
end # Mutant
|
||||
|
|
|
@ -4,6 +4,18 @@ module Mutant
|
|||
class Filter < self
|
||||
include Concord.new(:matcher, :predicate)
|
||||
|
||||
# Return new matcher
|
||||
#
|
||||
# @return [Matcher] matcher
|
||||
#
|
||||
# @return [Matcher]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(matcher, &predicate)
|
||||
new(matcher, predicate)
|
||||
end
|
||||
|
||||
# Enumerate matches
|
||||
#
|
||||
# @return [self]
|
||||
|
|
|
@ -3,7 +3,7 @@ module Mutant
|
|||
# Matcher for subjects that are a specific method
|
||||
class Method < self
|
||||
include Adamantium::Flat, Concord::Public.new(:env, :scope, :target_method)
|
||||
include AST::NodePredicates, Equalizer.new(:identification)
|
||||
include AST::NodePredicates
|
||||
|
||||
# Methods within rbx kernel directory are precompiled and their source
|
||||
# cannot be accessed via reading source location. Same for methods created by eval.
|
||||
|
|
|
@ -23,17 +23,6 @@ module Mutant
|
|||
super
|
||||
end
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
"#{scope.name}##{method_name}"
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
NAME_INDEX = 0
|
||||
|
||||
private
|
||||
|
|
|
@ -3,21 +3,9 @@ module Mutant
|
|||
class Method
|
||||
# Matcher for singleton methods
|
||||
class Singleton < self
|
||||
SUBJECT_CLASS = Subject::Method::Singleton
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
"#{scope.name}.#{method_name}"
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
RECEIVER_INDEX = 0
|
||||
NAME_INDEX = 1
|
||||
SUBJECT_CLASS = Subject::Method::Singleton
|
||||
RECEIVER_INDEX = 0
|
||||
NAME_INDEX = 1
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return method matcher class
|
||||
#
|
||||
# @return [Class:Matcher::Method]
|
||||
|
@ -46,8 +48,6 @@ module Mutant
|
|||
end
|
||||
memoize :methods
|
||||
|
||||
private
|
||||
|
||||
# Return subjects
|
||||
#
|
||||
# @return [Array<Subject>]
|
||||
|
|
|
@ -9,17 +9,6 @@ module Mutant
|
|||
Matcher::Methods::Instance
|
||||
].freeze
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
scope.name
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
# Enumerate subjects
|
||||
#
|
||||
# @return [self]
|
||||
|
|
|
@ -13,12 +13,12 @@ module Mutant
|
|||
|
||||
# Return method name
|
||||
#
|
||||
# @return [Symbol]
|
||||
# @return [Expression]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def name
|
||||
node.children[self.class::NAME_INDEX]
|
||||
node.children.fetch(self.class::NAME_INDEX)
|
||||
end
|
||||
|
||||
# Return match expression
|
||||
|
@ -28,7 +28,11 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def expression
|
||||
Expression.parse("#{context.identification}#{self.class::SYMBOL}#{name}")
|
||||
Expression::Method.new(
|
||||
scope_symbol: self.class::SYMBOL,
|
||||
scope_name: scope.name,
|
||||
method_name: name.to_s
|
||||
)
|
||||
end
|
||||
memoize :expression
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ module Mutant
|
|||
class Singleton < self
|
||||
|
||||
NAME_INDEX = 1
|
||||
SYMBOL = '.'.freeze
|
||||
SYMBOL = '.'.freeze
|
||||
|
||||
# Test if method is public
|
||||
#
|
||||
|
|
|
@ -35,7 +35,7 @@ require 'test_app'
|
|||
module Fixtures
|
||||
TEST_CONFIG = Mutant::Config::DEFAULT.update(reporter: Mutant::Reporter::Trace.new)
|
||||
TEST_CACHE = Mutant::Cache.new
|
||||
TEST_ENV = Mutant::Env::Bootstrap.call(TEST_CONFIG, TEST_CACHE)
|
||||
TEST_ENV = Mutant::Env::Bootstrap.(TEST_CONFIG, TEST_CACHE)
|
||||
end # Fixtures
|
||||
|
||||
module ParserHelper
|
||||
|
@ -46,6 +46,10 @@ module ParserHelper
|
|||
def parse(string)
|
||||
Unparser::Preprocessor.run(Parser::CurrentRuby.parse(string))
|
||||
end
|
||||
|
||||
def parse_expression(string)
|
||||
Mutant::Config::DEFAULT.expression_parser.(string)
|
||||
end
|
||||
end
|
||||
|
||||
module MessageHelper
|
||||
|
|
|
@ -73,7 +73,7 @@ RSpec.describe Mutant::CLI do
|
|||
|
||||
let(:default_matcher_config) do
|
||||
Mutant::Matcher::Config::DEFAULT
|
||||
.update(match_expressions: expressions.map(&Mutant::Expression.method(:parse)))
|
||||
.update(match_expressions: expressions.map(&method(:parse_expression)))
|
||||
end
|
||||
|
||||
let(:flags) { [] }
|
||||
|
@ -234,7 +234,7 @@ Options:
|
|||
let(:flags) { %w[--ignore-subject Foo::Bar] }
|
||||
|
||||
let(:expected_matcher_config) do
|
||||
default_matcher_config.update(subject_ignores: [Mutant::Expression.parse('Foo::Bar')])
|
||||
default_matcher_config.update(subject_ignores: [parse_expression('Foo::Bar')])
|
||||
end
|
||||
|
||||
it_should_behave_like 'a cli parser'
|
||||
|
|
11
spec/unit/mutant/context/scope_spec.rb
Normal file
11
spec/unit/mutant/context/scope_spec.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
RSpec.describe Mutant::Context::Scope do
|
||||
let(:object) { described_class.new(scope, source_path) }
|
||||
let(:scope) { double('scope', name: double('name')) }
|
||||
let(:source_path) { double('source path') }
|
||||
|
||||
describe '#identification' do
|
||||
subject { object.identification }
|
||||
|
||||
it { should be(scope.name) }
|
||||
end
|
||||
end
|
|
@ -9,22 +9,25 @@ RSpec.describe Mutant::Env do
|
|||
subjects: [],
|
||||
mutations: [],
|
||||
matchable_scopes: [],
|
||||
integration: Mutant::Integration::Null.new(config)
|
||||
integration: integration
|
||||
)
|
||||
end
|
||||
|
||||
let(:integration) { integration_class.new(config) }
|
||||
|
||||
let(:config) do
|
||||
Mutant::Config::DEFAULT.update(isolation: isolation)
|
||||
Mutant::Config::DEFAULT.update(isolation: isolation, integration: integration_class)
|
||||
end
|
||||
|
||||
let(:isolation) { double('Isolation') }
|
||||
let(:mutation) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
|
||||
let(:wrapped_node) { double('Wrapped Node') }
|
||||
let(:context) { double('Context') }
|
||||
let(:test_a) { double('Test A') }
|
||||
let(:test_b) { double('Test B') }
|
||||
let(:tests) { [test_a, test_b] }
|
||||
let(:selector) { double('Selector') }
|
||||
let(:isolation) { double('Isolation') }
|
||||
let(:mutation) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
|
||||
let(:wrapped_node) { double('Wrapped Node') }
|
||||
let(:context) { double('Context') }
|
||||
let(:test_a) { double('Test A') }
|
||||
let(:test_b) { double('Test B') }
|
||||
let(:tests) { [test_a, test_b] }
|
||||
let(:selector) { double('Selector') }
|
||||
let(:integration_class) { Mutant::Integration::Null }
|
||||
|
||||
let(:mutation_subject) do
|
||||
double(
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
RSpec.describe Mutant::Expression::Method do
|
||||
|
||||
let(:object) { described_class.parse(input) }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
let(:instance_method) { 'TestApp::Literal#string' }
|
||||
let(:singleton_method) { 'TestApp::Literal.string' }
|
||||
let(:object) { parse_expression(input) }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
let(:instance_method) { 'TestApp::Literal#string' }
|
||||
let(:singleton_method) { 'TestApp::Literal.string' }
|
||||
|
||||
describe '#match_length' do
|
||||
let(:input) { instance_method }
|
||||
|
@ -11,13 +10,13 @@ RSpec.describe Mutant::Expression::Method do
|
|||
subject { object.match_length(other) }
|
||||
|
||||
context 'when other is an equivalent expression' do
|
||||
let(:other) { described_class.parse(object.syntax) }
|
||||
let(:other) { parse_expression(object.syntax) }
|
||||
|
||||
it { should be(object.syntax.length) }
|
||||
end
|
||||
|
||||
context 'when other is an unequivalent expression' do
|
||||
let(:other) { described_class.parse('Foo*') }
|
||||
let(:other) { parse_expression('Foo*') }
|
||||
|
||||
it { should be(0) }
|
||||
end
|
||||
|
@ -30,19 +29,16 @@ RSpec.describe Mutant::Expression::Method do
|
|||
let(:input) { instance_method }
|
||||
|
||||
it 'returns correct matcher' do
|
||||
should eql(
|
||||
Mutant::Matcher::Method::Instance.new(
|
||||
env,
|
||||
TestApp::Literal, TestApp::Literal.instance_method(:string)
|
||||
)
|
||||
)
|
||||
expect(subject.map(&:expression)).to eql([object])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a singleton method' do
|
||||
let(:input) { singleton_method }
|
||||
|
||||
it { should eql(Mutant::Matcher::Method::Singleton.new(env, TestApp::Literal, TestApp::Literal.method(:string))) }
|
||||
it 'returns correct matcher' do
|
||||
expect(subject.map(&:expression)).to eql([object])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,45 +1,58 @@
|
|||
RSpec.describe Mutant::Expression::Methods do
|
||||
|
||||
let(:object) { described_class.parse(input) }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
let(:instance_methods) { 'TestApp::Literal#' }
|
||||
let(:singleton_methods) { 'TestApp::Literal.' }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
let(:object) { described_class.new(attributes) }
|
||||
|
||||
describe '#match_length' do
|
||||
let(:input) { instance_methods }
|
||||
let(:attributes) { { scope_name: 'TestApp::Literal', scope_symbol: '#' } }
|
||||
|
||||
subject { object.match_length(other) }
|
||||
|
||||
context 'when other is an equivalent expression' do
|
||||
let(:other) { described_class.parse(object.syntax) }
|
||||
let(:other) { parse_expression(object.syntax) }
|
||||
|
||||
it { should be(object.syntax.length) }
|
||||
end
|
||||
|
||||
context 'when other is matched' do
|
||||
let(:other) { described_class.parse('TestApp::Literal#foo') }
|
||||
let(:other) { parse_expression('TestApp::Literal#foo') }
|
||||
|
||||
it { should be(object.syntax.length) }
|
||||
end
|
||||
|
||||
context 'when other is an not matched expression' do
|
||||
let(:other) { described_class.parse('Foo*') }
|
||||
let(:other) { parse_expression('Foo*') }
|
||||
|
||||
it { should be(0) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#syntax' do
|
||||
subject { object.syntax }
|
||||
|
||||
context 'with an instance method' do
|
||||
let(:attributes) { { scope_name: 'TestApp::Literal', scope_symbol: '#' } }
|
||||
|
||||
it { should eql('TestApp::Literal#') }
|
||||
end
|
||||
|
||||
context 'with a singleton method' do
|
||||
let(:attributes) { { scope_name: 'TestApp::Literal', scope_symbol: '.' } }
|
||||
|
||||
it { should eql('TestApp::Literal.') }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#matcher' do
|
||||
subject { object.matcher(env) }
|
||||
|
||||
context 'with an instance method' do
|
||||
let(:input) { instance_methods }
|
||||
let(:attributes) { { scope_name: 'TestApp::Literal', scope_symbol: '#' } }
|
||||
|
||||
it { should eql(Mutant::Matcher::Methods::Instance.new(env, TestApp::Literal)) }
|
||||
end
|
||||
|
||||
context 'with a singleton method' do
|
||||
let(:input) { singleton_methods }
|
||||
let(:attributes) { { scope_name: 'TestApp::Literal', scope_symbol: '.' } }
|
||||
|
||||
it { should eql(Mutant::Matcher::Methods::Singleton.new(env, TestApp::Literal)) }
|
||||
end
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
RSpec.describe Mutant::Expression::Namespace::Exact do
|
||||
|
||||
let(:object) { described_class.parse(input) }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
let(:input) { 'TestApp::Literal' }
|
||||
let(:object) { parse_expression(input) }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
let(:input) { 'TestApp::Literal' }
|
||||
|
||||
describe '#matcher' do
|
||||
subject { object.matcher(env) }
|
||||
|
@ -14,13 +13,13 @@ RSpec.describe Mutant::Expression::Namespace::Exact do
|
|||
subject { object.match_length(other) }
|
||||
|
||||
context 'when other is an equivalent expression' do
|
||||
let(:other) { described_class.parse(object.syntax) }
|
||||
let(:other) { parse_expression(object.syntax) }
|
||||
|
||||
it { should be(object.syntax.length) }
|
||||
end
|
||||
|
||||
context 'when other is an unequivalent expression' do
|
||||
let(:other) { described_class.parse('Foo*') }
|
||||
let(:other) { parse_expression('Foo*') }
|
||||
|
||||
it { should be(0) }
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
RSpec.describe Mutant::Expression::Namespace::Recursive do
|
||||
|
||||
let(:object) { described_class.parse(input) }
|
||||
let(:input) { 'TestApp::Literal*' }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
let(:object) { parse_expression(input) }
|
||||
let(:input) { 'TestApp::Literal*' }
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
|
||||
describe '#matcher' do
|
||||
subject { object.matcher(env) }
|
||||
|
@ -10,48 +10,54 @@ RSpec.describe Mutant::Expression::Namespace::Recursive do
|
|||
it { should eql(Mutant::Matcher::Namespace.new(env, object)) }
|
||||
end
|
||||
|
||||
describe '#syntax' do
|
||||
subject { object.syntax }
|
||||
|
||||
it { should eql(input) }
|
||||
end
|
||||
|
||||
describe '#match_length' do
|
||||
subject { object.match_length(other) }
|
||||
|
||||
context 'when other is an equivalent expression' do
|
||||
let(:other) { described_class.parse(object.syntax) }
|
||||
let(:other) { parse_expression(object.syntax) }
|
||||
|
||||
it { should be(0) }
|
||||
end
|
||||
|
||||
context 'when other expression describes a shorter prefix' do
|
||||
let(:other) { described_class.parse('TestApp') }
|
||||
let(:other) { parse_expression('TestApp') }
|
||||
|
||||
it { should be(0) }
|
||||
end
|
||||
|
||||
context 'when other expression describes adjacent namespace' do
|
||||
let(:other) { described_class.parse('TestApp::LiteralFoo') }
|
||||
let(:other) { parse_expression('TestApp::LiteralFoo') }
|
||||
|
||||
it { should be(0) }
|
||||
end
|
||||
|
||||
context 'when other expression describes root namespace' do
|
||||
let(:other) { described_class.parse('TestApp::Literal') }
|
||||
let(:other) { parse_expression('TestApp::Literal') }
|
||||
|
||||
it { should be(16) }
|
||||
end
|
||||
|
||||
context 'when other expression describes a longer prefix' do
|
||||
context 'on constants' do
|
||||
let(:other) { described_class.parse('TestApp::Literal::Deep') }
|
||||
let(:other) { parse_expression('TestApp::Literal::Deep') }
|
||||
|
||||
it { should be(input[0..-2].length) }
|
||||
end
|
||||
|
||||
context 'on singleton method' do
|
||||
let(:other) { described_class.parse('TestApp::Literal.foo') }
|
||||
let(:other) { parse_expression('TestApp::Literal.foo') }
|
||||
|
||||
it { should be(input[0..-2].length) }
|
||||
end
|
||||
|
||||
context 'on instance method' do
|
||||
let(:other) { described_class.parse('TestApp::Literal#foo') }
|
||||
let(:other) { parse_expression('TestApp::Literal#foo') }
|
||||
|
||||
it { should be(input[0..-2].length) }
|
||||
end
|
||||
|
|
67
spec/unit/mutant/expression/parser_spec.rb
Normal file
67
spec/unit/mutant/expression/parser_spec.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
RSpec.describe Mutant::Expression::Parser do
|
||||
let(:object) { Mutant::Config::DEFAULT.expression_parser }
|
||||
|
||||
describe '#call' do
|
||||
subject { object.call(input) }
|
||||
|
||||
context 'on nonsense' do
|
||||
let(:input) { 'foo bar' }
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { subject }.to raise_error(
|
||||
Mutant::Expression::Parser::InvalidExpressionError,
|
||||
'Expression: "foo bar" is not valid'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on a valid expression' do
|
||||
let(:input) { 'Foo' }
|
||||
|
||||
it { should eql(Mutant::Expression::Namespace::Exact.new(scope_name: 'Foo')) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.try_parse' do
|
||||
subject { object.try_parse(input) }
|
||||
|
||||
context 'on nonsense' do
|
||||
let(:input) { 'foo bar' }
|
||||
|
||||
it { should be(nil) }
|
||||
end
|
||||
|
||||
context 'on a valid expression' do
|
||||
let(:input) { 'Foo' }
|
||||
|
||||
it { should eql(Mutant::Expression::Namespace::Exact.new(scope_name: 'Foo')) }
|
||||
end
|
||||
|
||||
context 'on ambiguous expression' do
|
||||
let(:object) { described_class.new([test_a, test_b]) }
|
||||
|
||||
let(:test_a) do
|
||||
Class.new(Mutant::Expression) do
|
||||
include Anima.new
|
||||
const_set(:REGEXP, /\Atest-syntax\z/.freeze)
|
||||
end
|
||||
end
|
||||
|
||||
let(:test_b) do
|
||||
Class.new(Mutant::Expression) do
|
||||
include Anima.new
|
||||
const_set(:REGEXP, /^test-syntax$/.freeze)
|
||||
end
|
||||
end
|
||||
|
||||
let(:input) { 'test-syntax' }
|
||||
|
||||
it 'raises expected exception' do
|
||||
expect { subject }.to raise_error(
|
||||
Mutant::Expression::Parser::AmbiguousExpressionError,
|
||||
'Ambiguous expression: "test-syntax"'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,99 +1,47 @@
|
|||
RSpec.describe Mutant::Expression do
|
||||
let(:object) { described_class }
|
||||
|
||||
describe '.try_parse' do
|
||||
subject { object.try_parse(input) }
|
||||
|
||||
context 'on nonsense' do
|
||||
let(:input) { 'foo bar' }
|
||||
|
||||
it { should be(nil) }
|
||||
end
|
||||
|
||||
context 'on a valid expression' do
|
||||
let(:input) { 'Foo' }
|
||||
|
||||
it { should eql(Mutant::Expression::Namespace::Exact.new('Foo')) }
|
||||
end
|
||||
|
||||
context 'on ambiguous expression' do
|
||||
class ExpressionA < Mutant::Expression
|
||||
register(/\Atest-syntax\z/)
|
||||
end
|
||||
|
||||
class ExpressionB < Mutant::Expression
|
||||
register(/^test-syntax$/)
|
||||
end
|
||||
|
||||
let(:input) { 'test-syntax' }
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { subject }.to raise_error(
|
||||
Mutant::Expression::AmbiguousExpressionError,
|
||||
'Ambiguous expression: "test-syntax"'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:parser) { Mutant::Config::DEFAULT.expression_parser }
|
||||
|
||||
describe '#prefix?' do
|
||||
let(:object) { described_class.parse('Foo*') }
|
||||
let(:object) { parser.call('Foo*') }
|
||||
|
||||
subject { object.prefix?(other) }
|
||||
|
||||
context 'when object is a prefix of other' do
|
||||
let(:other) { described_class.parse('Foo::Bar') }
|
||||
let(:other) { parser.call('Foo::Bar') }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context 'when other is not a prefix of other' do
|
||||
let(:other) { described_class.parse('Bar') }
|
||||
let(:other) { parser.call('Bar') }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#inspect' do
|
||||
let(:object) { described_class.parse('Foo') }
|
||||
describe '.try_parse' do
|
||||
let(:object) do
|
||||
Class.new(described_class) do
|
||||
include Anima.new(:foo)
|
||||
|
||||
subject { object.inspect }
|
||||
|
||||
it { should eql('<Mutant::Expression: Foo>') }
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
describe '#_dump' do
|
||||
let(:object) { described_class.parse('Foo') }
|
||||
subject { object._dump(double('Level')) }
|
||||
|
||||
it { should eql('Foo') }
|
||||
end
|
||||
|
||||
describe '.parse' do
|
||||
subject { object.parse(input) }
|
||||
|
||||
context 'on nonsense' do
|
||||
let(:input) { 'foo bar' }
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { subject }.to raise_error(
|
||||
Mutant::Expression::InvalidExpressionError,
|
||||
'Expression: "foo bar" is not valid'
|
||||
)
|
||||
const_set(:REGEXP, /(?<foo>foo)/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on a valid expression' do
|
||||
let(:input) { 'Foo' }
|
||||
subject { object.try_parse(input) }
|
||||
|
||||
it { should eql(Mutant::Expression::Namespace::Exact.new('Foo')) }
|
||||
context 'on succesful parse' do
|
||||
let(:input) { 'foo' }
|
||||
|
||||
it { should eql(object.new(foo: 'foo')) }
|
||||
end
|
||||
|
||||
context 'on unsuccesful parse' do
|
||||
let(:input) { 'bar' }
|
||||
|
||||
it { should be(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '._load' do
|
||||
subject { described_class._load('Foo') }
|
||||
|
||||
it { should eql(described_class.parse('Foo')) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -97,19 +97,19 @@ RSpec.describe Mutant::Integration::Rspec do
|
|||
[
|
||||
Mutant::Test.new(
|
||||
id: 'rspec:0:example-a-location/example-a-full-description',
|
||||
expression: Mutant::Expression.parse('*')
|
||||
expression: parse_expression('*')
|
||||
),
|
||||
Mutant::Test.new(
|
||||
id: 'rspec:1:example-c-location/Example::C blah',
|
||||
expression: Mutant::Expression.parse('Example::C')
|
||||
expression: parse_expression('Example::C')
|
||||
),
|
||||
Mutant::Test.new(
|
||||
id: "rspec:2:example-d-location/Example::D\nblah",
|
||||
expression: Mutant::Expression.parse('*')
|
||||
expression: parse_expression('*')
|
||||
),
|
||||
Mutant::Test.new(
|
||||
id: 'rspec:3:example-e-location/Example::E',
|
||||
expression: Mutant::Expression.parse('Foo')
|
||||
expression: parse_expression('Foo')
|
||||
)
|
||||
]
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
RSpec.describe Mutant::Matcher::Compiler::SubjectPrefix do
|
||||
let(:object) { described_class.new(Mutant::Expression.parse('Foo*')) }
|
||||
let(:object) { described_class.new(parse_expression('Foo*')) }
|
||||
|
||||
let(:_subject) { double('Subject', expression: Mutant::Expression.parse(subject_expression)) }
|
||||
let(:_subject) { double('Subject', expression: parse_expression(subject_expression)) }
|
||||
|
||||
describe '#call' do
|
||||
subject { object.call(_subject) }
|
||||
|
|
|
@ -3,8 +3,8 @@ RSpec.describe Mutant::Matcher::Compiler do
|
|||
|
||||
let(:env) { Fixtures::TEST_ENV }
|
||||
|
||||
let(:expression_a) { Mutant::Expression.parse('Foo*') }
|
||||
let(:expression_b) { Mutant::Expression.parse('Bar*') }
|
||||
let(:expression_a) { parse_expression('Foo*') }
|
||||
let(:expression_b) { parse_expression('Bar*') }
|
||||
|
||||
let(:matcher_a) { expression_a.matcher(env) }
|
||||
let(:matcher_b) { expression_b.matcher(env) }
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
RSpec.describe Mutant::Matcher::Filter do
|
||||
let(:object) { described_class.new(matcher, predicate) }
|
||||
let(:object) { described_class.new(matcher, predicate) }
|
||||
let(:matcher) { [subject_a, subject_b] }
|
||||
let(:subject_a) { double('Subject A') }
|
||||
let(:subject_b) { double('Subject B') }
|
||||
|
||||
describe '#each' do
|
||||
let(:yields) { [] }
|
||||
subject { object.each { |entry| yields << entry } }
|
||||
|
||||
let(:matcher) { [subject_a, subject_b] }
|
||||
let(:predicate) { ->(node) { node.eql?(subject_a) } }
|
||||
|
||||
let(:subject_a) { double('Subject A') }
|
||||
let(:subject_b) { double('Subject B') }
|
||||
|
||||
# it_should_behave_like 'an #each method'
|
||||
context 'with no block' do
|
||||
subject { object.each }
|
||||
|
@ -26,4 +25,12 @@ RSpec.describe Mutant::Matcher::Filter do
|
|||
expect { subject }.to change { yields }.from([]).to([subject_a])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.build' do
|
||||
subject { described_class.build(matcher, &predicate) }
|
||||
|
||||
let(:predicate) { ->(_subject) { false } }
|
||||
|
||||
its(:to_a) { should eql([]) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
RSpec.describe Mutant::Matcher::Namespace do
|
||||
let(:object) { described_class.new(env, Mutant::Expression.parse('TestApp*')) }
|
||||
let(:yields) { [] }
|
||||
let(:env) { double('Env') }
|
||||
let(:object) { described_class.new(env, parse_expression('TestApp*')) }
|
||||
let(:yields) { [] }
|
||||
let(:env) { double('Env') }
|
||||
|
||||
subject { object.each { |item| yields << item } }
|
||||
|
||||
|
@ -22,7 +22,7 @@ RSpec.describe Mutant::Matcher::Namespace do
|
|||
|
||||
allow(env).to receive(:matchable_scopes).and_return(
|
||||
[singleton_a, singleton_b, singleton_c].map do |scope|
|
||||
Mutant::Matcher::Scope.new(env, scope, Mutant::Expression.parse(scope.name))
|
||||
Mutant::Matcher::Scope.new(env, scope, parse_expression(scope.name))
|
||||
end
|
||||
)
|
||||
end
|
||||
|
|
|
@ -3,27 +3,27 @@ RSpec.describe Mutant::Selector::Expression do
|
|||
let(:object) { described_class.new(integration) }
|
||||
|
||||
let(:subject_class) do
|
||||
parse = method(:parse_expression)
|
||||
|
||||
Class.new(Mutant::Subject) do
|
||||
def expression
|
||||
Mutant::Expression.parse('SubjectA')
|
||||
define_method(:expression) do
|
||||
parse.('SubjectA')
|
||||
end
|
||||
|
||||
def match_expressions
|
||||
[expression] << Mutant::Expression.parse('SubjectB')
|
||||
define_method(:match_expressions) do
|
||||
[expression] << parse.('SubjectB')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:mutation_subject) { subject_class.new(context, node) }
|
||||
let(:context) { double('Context') }
|
||||
let(:node) { double('Node') }
|
||||
|
||||
let(:config) { Mutant::Config::DEFAULT.update(integration: integration) }
|
||||
let(:integration) { double('Integration', all_tests: all_tests) }
|
||||
|
||||
let(:test_a) { double('test a', expression: Mutant::Expression.parse('SubjectA')) }
|
||||
let(:test_b) { double('test b', expression: Mutant::Expression.parse('SubjectB')) }
|
||||
let(:test_c) { double('test c', expression: Mutant::Expression.parse('SubjectC')) }
|
||||
let(:mutation_subject) { subject_class.new(context, node) }
|
||||
let(:context) { double('Context') }
|
||||
let(:node) { double('Node') }
|
||||
let(:config) { Mutant::Config::DEFAULT.update(integration: integration) }
|
||||
let(:integration) { double('Integration', all_tests: all_tests) }
|
||||
let(:test_a) { double('test a', expression: parse_expression('SubjectA')) }
|
||||
let(:test_b) { double('test b', expression: parse_expression('SubjectB')) }
|
||||
let(:test_c) { double('test c', expression: parse_expression('SubjectC')) }
|
||||
|
||||
subject { object.call(mutation_subject) }
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ RSpec.describe Mutant::Subject::Method::Instance do
|
|||
describe '#expression' do
|
||||
subject { object.expression }
|
||||
|
||||
it { should eql(Mutant::Expression.parse('Test#foo')) }
|
||||
it { should eql(parse_expression('Test#foo')) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
@ -34,7 +34,7 @@ RSpec.describe Mutant::Subject::Method::Instance do
|
|||
describe '#match_expression' do
|
||||
subject { object.match_expressions }
|
||||
|
||||
it { should eql(%w[Test#foo Test*].map(&Mutant::Expression.method(:parse))) }
|
||||
it { should eql(%w[Test#foo Test*].map(&method(:parse_expression))) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ RSpec.describe Mutant::Subject::Method::Singleton do
|
|||
describe '#expression' do
|
||||
subject { object.expression }
|
||||
|
||||
it { should eql(Mutant::Expression.parse('Test.foo')) }
|
||||
it { should eql(parse_expression('Test.foo')) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
@ -29,7 +29,7 @@ RSpec.describe Mutant::Subject::Method::Singleton do
|
|||
describe '#match_expression' do
|
||||
subject { object.match_expressions }
|
||||
|
||||
it { should eql(%w[Test.foo Test*].map(&Mutant::Expression.method(:parse))) }
|
||||
it { should eql(%w[Test.foo Test*].map(&method(:parse_expression))) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
|
|
@ -2,11 +2,14 @@ RSpec.describe Mutant::Subject do
|
|||
let(:class_under_test) do
|
||||
Class.new(described_class) do
|
||||
def expression
|
||||
Mutant::Expression.parse('SubjectA')
|
||||
Mutant::Expression::Namespace::Exact.new(scope_name: 'SubjectA')
|
||||
end
|
||||
|
||||
def match_expressions
|
||||
[expression] << Mutant::Expression.parse('SubjectB')
|
||||
[
|
||||
expression,
|
||||
Mutant::Expression::Namespace::Exact.new(scope_name: 'SubjectB')
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue