Slightly refactor CLI

* Backlists should "work" now.
* CLI (match expression) classifiers are not used as matchers anymore
This commit is contained in:
Markus Schirp 2013-09-13 22:28:04 +02:00
parent 278e51ac8d
commit e9931f6abe
16 changed files with 279 additions and 149 deletions

View file

@ -38,6 +38,7 @@ require 'mutant/predicate'
require 'mutant/predicate/attribute'
require 'mutant/predicate/whitelist'
require 'mutant/predicate/blacklist'
require 'mutant/predicate/matcher'
require 'mutant/mutator'
require 'mutant/mutation'
require 'mutant/mutation/evil'

View file

@ -45,9 +45,8 @@ module Mutant
@debug = @fail_fast = @zombie = false
@cache = Mutant::Cache.new
@strategy_builder = nil
parse(arguments)
config # trigger lazyness
config # trigger lazyness now
end
# Return config
@ -58,61 +57,20 @@ module Mutant
#
def config
Config.new(
cache: @cache,
zombie: @zombie,
debug: debug?,
matcher: matcher,
filter: filter,
fail_fast: @fail_fast,
strategy: strategy,
reporter: reporter
cache: @cache,
zombie: @zombie,
debug: @debug,
matcher: matcher,
subject_predicate: @subject_predicate.output,
strategy: @strategy.output,
fail_fast: @fail_fast,
reporter: reporter
)
end
memoize :config
private
# Test for running in debug mode
#
# @return [true]
# if debug mode is active
#
# @return [false]
# otherwise
#
# @api private
#
def debug?
@debug
end
# Return mutation filter
#
# @return [Mutant::Matcher]
#
# @api private
#
def filter
if @filters.empty?
Predicate::ALL
else
Predicate::Whitelist.new(@filters)
end
end
# Return stratety
#
# @return [Strategy]
#
# @api private
#
def strategy
unless @strategy_builder
raise(Error, 'No strategy was set!')
end
@strategy_builder.strategy
end
# Return reporter
#
# @return [Mutant::Reporter::CLI]
@ -173,14 +131,14 @@ module Mutant
add_options(builder)
end
matchers =
patterns =
begin
opts.parse!(arguments)
rescue OptionParser::ParseError => error
raise(Error, error.message, error.backtrace)
end
parse_matchers(matchers)
parse_matchers(patterns)
end
# Parse matchers
@ -210,14 +168,12 @@ module Mutant
parser.separator(EMPTY_STRING)
parser.separator('Strategies:')
writer = lambda do |builder|
@strategy_builder = builder
end
[
Builder::Rspec
].each do |builder|
builder.add_options(parser, &writer)
{
Builder::Rspec => :@strategy,
Builder::Predicate::Subject => :@subject_predicate,
}.each do |builder, instance_variable_name|
builder = builder.new(@cache, parser)
instance_variable_set(instance_variable_name, builder)
end
end

View file

@ -6,6 +6,53 @@ module Mutant
class Builder
include AbstractType
# Return cache
#
# @return [Cache]
#
# @api private
#
attr_reader :cache
private :cache
# Return parser
#
# @return [OptionParser]
#
# @api private
#
attr_reader :parser
private :parser
# Initialize builder
#
# @param [OptionParser] parser
#
# @api privateo
#
def initialize(cache, parser)
@cache, @parser = cache, parser
add_options
end
# Add cli options
#
# @param [OptionParser]
#
# @return [self]
#
# @api private
#
abstract_method :add_options
# Return build output
#
# @return [Object]
#
# @api private
#
abstract_method :output
# Rspec strategy builder
class Rspec < self
@ -15,10 +62,28 @@ module Mutant
#
# @api private
#
def initialize
def initialize(*)
@level = 0
@rspec = false
super
end
# Return strategy
#
# @return [Strategy::Rspec]
#
# @api private
#
def output
unless @rspec
raise Error, 'No strategy given'
end
Strategy::Rspec.new(@level)
end
private
# Set rspec level
#
# @return [self]
@ -30,16 +95,6 @@ module Mutant
self
end
# Return strategy
#
# @return [Strategy::Rspec]
#
# @api private
#
def strategy
Strategy::Rspec.new(@level)
end
# Add cli options
#
# @param [OptionParser] parser
@ -48,17 +103,76 @@ module Mutant
#
# @api private
#
def self.add_options(parser)
builder = new
def add_options
parser.on('--rspec', 'kills mutations with rspec') do
yield builder
@rspec = true
end
parser.on('--rspec-level LEVEL', 'set rspec expansion level') do |level|
builder.set_level(level.to_i)
@level = level.to_i
end
end
end # Rspec
# Abstract predicate builder
class Predicate < self
class Subject < self
# Initialize object
#
# @api private
#
# @return [undefined]
#
def initialize(*)
super
@predicates = []
end
# Return predicate
#
# @api private
#
def output
if @predicates.empty?
Mutant::Predicate::CONTRADICTION
else
Mutant::Predicate::Whitelist.new(@predicates)
end
end
private
# Add cli options
#
# @return [undefined]
#
# @api private
#
def add_options
parser.on('--ignore-subject MATCHER', 'ignores subjects that matches MATCHER') do |pattern|
add_pattern(pattern)
end
end
# Add matcher to predicates
#
# @param [String] pattern
#
# @api private
#
def add_pattern(pattern)
matcher = Classifier.run(@cache, pattern)
@predicates << Mutant::Predicate::Matcher.new(matcher)
end
end
end # Predicate
end # Builder
end # CLI
end # Mutant

View file

@ -57,7 +57,7 @@ module Mutant
# @param [Cache] cache
# @param [String] pattern
#
# @return [Classifier]
# @return [Matcher]
# if a classifier handles the input
#
# @raise [RuntimeError]
@ -72,7 +72,7 @@ module Mutant
raise Error, "No matcher handles: #{pattern.inspect}"
when 1
klass, match = matches.first
klass.new(cache, match)
klass.new(cache, match).matcher
else
raise Error, "More than one matcher found for: #{pattern.inspect}"
end

View file

@ -22,8 +22,6 @@ module Mutant
register(REGEXP)
private
# Return method matcher
#
# @return [Matcher::Method]
@ -35,6 +33,8 @@ module Mutant
end
memoize :matcher
private
# Return method
#
# @return [Method, UnboundMethod]

View file

@ -7,8 +7,6 @@ module Mutant
# Namespace classifier
class Namespace < self
private
# Return matcher
#
# @return [Matcher]
@ -19,6 +17,8 @@ module Mutant
self.class::MATCHER.new(cache, namespace)
end
private
# Return namespace
#
# @return [Class, Module]

View file

@ -4,8 +4,14 @@ module Mutant
# The configuration of a mutator run
class Config
include Adamantium::Flat, Anima.new(
:cache, :debug, :strategy, :matcher, :filter,
:reporter, :fail_fast, :zombie
:cache,
:debug,
:strategy,
:matcher,
:subject_predicate,
:reporter,
:fail_fast,
:zombie
)
# Enumerate subjects
@ -22,7 +28,7 @@ module Mutant
#
def subjects(&block)
return to_enum(__method__) unless block_given?
matcher.each(&block)
Matcher::Filter.new(matcher, subject_predicate).each(&block)
self
end

View file

@ -53,8 +53,25 @@ module Mutant
nil
end
# Mutation predicate matching all mutations
Mutant.singleton_subclass_instance('ALL', self) do
# Mutation predicate matching no inputs
Mutant.singleton_subclass_instance('CONTRADICTION', self) do
# Test for match
#
# @pram [Mutation] _mutation
#
# @return [true]
#
# @api private
#
def match?(_mutation)
false
end
end
# Mutation predicate matching all inputs
Mutant.singleton_subclass_instance('TAUTOLOGY', self) do
# Test for match
#

View file

@ -0,0 +1,36 @@
module Mutant
class Predicate
# Return matcher
class Matcher < self
include Concord.new(:matcher)
# Test if subject matches
#
# @param [Subject] subject
#
# @return [true]
# if subject is handled by matcher
#
# @return [false]
# otherwise
#
def match?(subject)
subjects.include?(subject)
end
private
# Return subjects matched by matcher
#
# @return [Set<Subject>]
#
# @api private
#
def subjects
matcher.to_a.to_set
end
memoize :subjects
end # Matcher
end # Predicate
end # Mutant

View file

@ -2,6 +2,7 @@
module Mutant
class Predicate
# Whiltelist filter
class Whitelist < self
include Adamantium::Flat, Concord.new(:whitelist)

View file

@ -20,9 +20,9 @@ module Mutant
#
def run
info 'Mutant configuration:'
info 'Matcher: %s', object.matcher.inspect
info 'Filter: %s', object.filter.inspect
info 'Strategy: %s', object.strategy.inspect
info 'Matcher: %s', object.matcher.inspect
info 'Subject Filter: %s', object.subject_predicate.inspect
info 'Strategy: %s', object.strategy.inspect
self
end

View file

@ -11,16 +11,21 @@ describe Mutant, 'rspec integration' do
end
specify 'it allows to kill mutations' do
Kernel.system('bundle exec mutant --rspec ::TestApp::Literal#string').should be(true)
Kernel.system('bundle exec mutant -I lib --require test_app --rspec ::TestApp::Literal#string').should be(true)
end
specify 'it allows to exclude mutations' do
Kernel.system('bundle exec mutant -I lib --require test_app --rspec ::TestApp::Literal#string --ignore-subject ::TestApp::Literal#uncovered_string').should be(true)
end
specify 'fails to kill mutations when they are not covered' do
cli = 'bundle exec mutant --rspec ::TestApp::Literal#uncovered_string'
cli = 'bundle exec mutant -I lib --require test_app --rspec ::TestApp::Literal#uncovered_string'
Kernel.system(cli).should be(false)
end
specify 'fails when some mutations are not covered' do
cli = 'bundle exec mutant --rspec ::TestApp::Literal'
cli = 'bundle exec mutant -I lib --require test_app --rspec ::TestApp::Literal'
Kernel.system(cli).should be(false)
end
end

View file

@ -4,39 +4,35 @@ require 'spec_helper'
describe Mutant::CLI::Builder::Rspec do
let(:object) { described_class.new }
let(:level) { double('Level') }
let(:option_parser) { OptionParser.new }
let(:cache) { Mutant::Cache.new }
let(:object) { described_class.new(cache, option_parser) }
let(:level) { double('Level') }
let(:default_strategy) do
Mutant::Strategy::Rspec.new(0)
end
let(:altered_strategy) do
Mutant::Strategy::Rspec.new(level)
Mutant::Strategy::Rspec.new(1)
end
describe '#set_level' do
subject { object.set_level(level) }
describe 'default' do
specify do
object
option_parser.parse!(%w[--rspec])
expect(object.output).to eql(default_strategy)
end
end
describe 'parsing a level' do
specify do
expect { subject }.to change { object.strategy }.from(default_strategy).to(altered_strategy)
object
option_parser.parse!(%w[--rspec --rspec-level 1])
expect(object.output).to eql(altered_strategy)
end
end
describe '#strategy' do
subject { object.strategy }
context 'without setting a level' do
it { should eql(Mutant::Strategy::Rspec.new(0)) }
end
context 'with setting a level' do
before do
object.set_level(level)
end
it { should eql(Mutant::Strategy::Rspec.new(level)) }
end
end
end

View file

@ -3,6 +3,7 @@
require 'spec_helper'
describe Mutant::CLI::Classifier::Method, '#each' do
let(:object) { described_class.run(cache, input) }
let(:cache) { Mutant::Cache.new }
let(:instance_method) { '::TestApp::Literal#string' }
@ -72,18 +73,5 @@ describe Mutant::CLI::Classifier::Method, '#each' do
.to yield_with_args(Mutant::Subject::Method::Singleton)
end
end
context 'with an unknown method' do
let(:input) { unknown_method }
it 'returns an enumerator' do
should be_instance_of(to_enum.class)
end
it 'raises an exception when #each is called' do
expect { subject.each {} }
.to raise_error(NameError, "Cannot find method #{input}")
end
end
end
end

View file

@ -11,8 +11,7 @@ describe Mutant::CLI::Classifier, '.run' do
shared_examples_for this_spec do
it 'shoud return expected instance' do
regexp = expected_class::REGEXP
should eql(expected_class.new(cache, regexp.match(input)))
should eql(expected_matcher)
end
let(:expected_class) { Mutant::CLI::Classifier::Method }
@ -22,20 +21,32 @@ describe Mutant::CLI::Classifier, '.run' do
let(:input) { '::TestApp::Literal#string' }
it_should_behave_like this_spec
let(:expected_matcher) do
Mutant::Matcher::Method::Instance.new(cache, TestApp::Literal, TestApp::Literal.instance_method(:string))
end
include_examples this_spec
end
context 'with instance method notation' do
let(:input) { 'TestApp::Literal#string' }
it_should_behave_like this_spec
let(:expected_matcher) do
Mutant::Matcher::Method::Instance.new(cache, TestApp::Literal, TestApp::Literal.instance_method(:string))
end
include_examples this_spec
end
context 'with singleton method notation' do
let(:input) { 'TestApp::Literal.string' }
it_should_behave_like this_spec
let(:expected_matcher) do
Mutant::Matcher::Method::Singleton.new(cache, TestApp::Literal, TestApp::Literal.method(:string))
end
include_examples this_spec
end
context 'with invalid notation' do

View file

@ -13,7 +13,6 @@ end
shared_examples_for 'a cli parser' do
subject { cli.config }
its(:filter) { should eql(expected_filter) }
its(:strategy) { should eql(expected_strategy) }
its(:reporter) { should eql(expected_reporter) }
its(:matcher) { should eql(expected_matcher) }
@ -28,12 +27,12 @@ describe Mutant::CLI, '.new' do
end
# Defaults
let(:expected_filter) { Mutant::Predicate::ALL }
let(:expected_filter) { Mutant::Predicate::TAUTOLOGY }
let(:expected_strategy) { Mutant::Strategy::Rspec.new(0) }
let(:expected_reporter) { Mutant::Reporter::CLI.new($stdout) }
let(:ns) { Mutant::CLI::Classifier }
let(:cache) { Mutant::Cache.new }
let(:ns) { Mutant::Matcher }
let(:cache) { Mutant::Cache.new }
let(:cli) { object.new(arguments) }
@ -57,7 +56,7 @@ describe Mutant::CLI, '.new' do
context 'with many strategy flags' do
let(:arguments) { %w(--rspec --rspec TestApp) }
let(:expected_matcher) { Mutant::CLI::Classifier::Namespace::Flat.new(Mutant::Cache.new, 'TestApp') }
let(:expected_matcher) { Mutant::Matcher::Scope.new(cache, TestApp) }
it_should_behave_like 'a cli parser'
end
@ -79,15 +78,15 @@ describe Mutant::CLI, '.new' do
context 'with explicit method matcher' do
let(:arguments) { %w(--rspec TestApp::Literal#float) }
let(:expected_matcher) { ns::Method.new(cache, 'TestApp::Literal#float') }
let(:expected_matcher) { ns::Method::Instance.new(cache, TestApp::Literal, TestApp::Literal.instance_method(:float)) }
it_should_behave_like 'a cli parser'
end
context 'with debug flag' do
let(:matcher) { '::TestApp*' }
let(:arguments) { %W(--debug --rspec #{matcher}) }
let(:expected_matcher) { ns::Namespace::Recursive.new(cache, matcher) }
let(:matcher) { '::TestApp*' }
let(:arguments) { %W(--debug --rspec #{matcher}) }
let(:expected_matcher) { ns::Namespace.new(cache, TestApp) }
it_should_behave_like 'a cli parser'
@ -97,9 +96,9 @@ describe Mutant::CLI, '.new' do
end
context 'with zombie flag' do
let(:matcher) { '::TestApp*' }
let(:arguments) { %W(--zombie --rspec #{matcher}) }
let(:expected_matcher) { ns::Namespace::Recursive.new(cache, matcher) }
let(:matcher) { '::TestApp*' }
let(:arguments) { %W(--zombie --rspec #{matcher}) }
let(:expected_matcher) { ns::Namespace.new(cache, TestApp) }
it_should_behave_like 'a cli parser'
@ -109,9 +108,9 @@ describe Mutant::CLI, '.new' do
end
context 'with namespace matcher' do
let(:matcher) { '::TestApp*' }
let(:arguments) { %W(--rspec #{matcher}) }
let(:expected_matcher) { ns::Namespace::Recursive.new(cache, matcher) }
let(:matcher) { '::TestApp*' }
let(:arguments) { %W(--rspec #{matcher}) }
let(:expected_matcher) { ns::Namespace.new(cache, TestApp) }
it_should_behave_like 'a cli parser'
end
@ -127,7 +126,7 @@ describe Mutant::CLI, '.new' do
]
end
let(:expected_matcher) { ns::Method.new(cache, 'TestApp::Literal#float') }
let(:expected_matcher) { ns::Method::Instance.new(cache, TestApp::Literal, TestApp::Literal.instance_method(:float)) }
let(:expected_filter) { Mutant::Predicate::Whitelist.new(filters) }
it_should_behave_like 'a cli parser'