Add support for --fail-fast flag

* Some long planned runner design changes.
* It now also uses the emitter pattern
This commit is contained in:
Markus Schirp 2013-07-15 01:17:15 +02:00
parent e8ecdab025
commit 8a6a51fd08
16 changed files with 279 additions and 215 deletions

View file

@ -56,12 +56,13 @@ module Mutant
#
def config
Config.new(
:cache => @cache,
:debug => debug?,
:matcher => matcher,
:filter => filter,
:strategy => strategy,
:reporter => reporter
:cache => @cache,
:debug => debug?,
:matcher => matcher,
:filter => filter,
:fail_fast => !!@fail_fast,
:strategy => strategy,
:reporter => reporter
)
end
memoize :config
@ -151,6 +152,16 @@ module Mutant
@filters << klass.new(filter)
end
# Set fail fast
#
# @api private
#
# @return [undefined]
#
def set_fail_fast
@fail_fast = true
end
# Set debug mode
#
# @api private
@ -252,6 +263,8 @@ module Mutant
opts.on('--code FILTER', 'Adds a code filter') do |filter|
add_filter Mutation::Filter::Code, filter
end.on('--fail-fast', 'Fail fast') do
set_fail_fast
end.on('-d','--debug', 'Enable debugging output') do
set_debug
end.on_tail('-h', '--help', 'Show this message') do

View file

@ -2,7 +2,7 @@ module Mutant
# The configuration of a mutator run
class Config
include Adamantium::Flat, Anima.new(
:cache, :debug, :strategy, :matcher, :filter, :reporter
:cache, :debug, :strategy, :matcher, :filter, :reporter, :fail_fast
)
# Enumerate subjects

View file

@ -1,8 +1,57 @@
module Mutant
# Runner baseclass
class Runner
include Adamantium::Flat, AbstractType, Equalizer.new(:config)
extend MethodObject
include Adamantium::Flat, AbstractType
REGISTRY = {}
# Register handler
#
# @param [Class] klass
#
# @return [undefined]
#
# @api private
#
def self.register(klass)
REGISTRY[klass]=self
end
private_class_method :register
# Lookup runner
#
# @param [Class] klass
#
# @return [undefined]
#
# @api private
#
def self.lookup(klass)
current = klass
while current
handler = REGISTRY.fetch(current) do
current = current.superclass; nil
end
return handler if handler
end
raise ArgumentError, "No handler for: #{klass}"
end
private_class_method :lookup
# Run runner for object
#
# @param [Config] config
# @param [Object] object
#
# @return [Runner]
#
# @api private
#
def self.run(config, object)
handler = lookup(object.class)
handler.new(config, object)
end
# Return config
#
@ -27,6 +76,18 @@ module Mutant
@end = Time.now
end
# Test if runner should stop
#
# @return [true]
# if runner should stop
#
# @return [false]
# otherwise
#
def stop?
!!@stop
end
# Return runtime
#
# @return [Float]
@ -34,22 +95,17 @@ module Mutant
# @api private
#
def runtime
@end - @start
(@end || Time.now) - @start
end
memoize :runtime
# Test if runner failed
# Return reporter
#
# @return [true]
# if failed
#
# @return [false]
# otherwise
# @return [Reporter]
#
# @api private
#
def failed?
!success?
def reporter
config.reporter
end
# Test if runner is successful
@ -64,16 +120,6 @@ module Mutant
#
abstract_method :success?
# Return reporter
#
# @return [Reporter]
#
# @api private
#
def reporter
config.reporter
end
private
# Perform operation
@ -96,5 +142,36 @@ module Mutant
reporter.report(object)
end
# Perform dispatch
#
# @return [Enumerable<Runner>]
#
# @api private
#
def dispatch(input)
collection = []
input.each do |object|
runner = visit(object)
collection << runner
if runner.stop?
@stop = true
break
end
end
collection
end
# Visit object
#
# @param [Object] object
#
# @return [undefined]
#
# @api private
#
def visit(object)
Runner.run(config, object)
end
end # Runner
end # Mutant

View file

@ -3,6 +3,22 @@ module Mutant
# Runner for object config
class Config < self
register Mutant::Config
# Run runner for object
#
# @param [Config] config
# @param [Object] object
#
# @return [Runner]
#
# @api private
#
def self.run(config)
handler = lookup(config.class)
handler.new(config)
end
# Return subject runners
#
# @return [Enumerable<Runner::Subject>]
@ -18,7 +34,7 @@ module Mutant
# @api private
#
def failed_subjects
subjects.select(&:failed?)
subjects.reject(&:success?)
end
memoize :failed_subjects
@ -58,9 +74,7 @@ module Mutant
def run_subjects
strategy = self.strategy
strategy.setup
@subjects = config.subjects.map do |subject|
Subject.run(config, subject)
end
@subjects = dispatch(config.subjects)
strategy.teardown
end

View file

@ -4,6 +4,8 @@ module Mutant
class Mutation < self
include Concord::Public.new(:config, :mutation)
register Mutant::Mutation
# Return killer instance
#
# @return [Killer]
@ -50,7 +52,8 @@ module Mutant
#
def run
@killer = config.strategy.kill(mutation)
report(@killer)
report(killer)
@stop = config.fail_fast && !killer.success?
end
end # Mutation

View file

@ -4,13 +4,7 @@ module Mutant
class Subject < self
include Concord::Public.new(:config, :subject)
# Return subject
#
# @return [Subject]
#
# @api private
#
attr_reader :subject
register Mutant::Subject
# Initialize object
#
@ -41,7 +35,7 @@ module Mutant
# @api private
#
def failed_mutations
mutations.select(&:failed?)
mutations.reject(&:success?)
end
memoize :failed_mutations
@ -70,9 +64,7 @@ module Mutant
def run
subject = self.subject
report(subject)
@mutations = subject.map do |mutation|
Mutation.run(config, mutation)
end
@mutations = dispatch(subject.mutations)
report(self)
end

View file

@ -3,38 +3,18 @@ module Mutant
class Subject
include AbstractType, Adamantium::Flat, Enumerable, Concord::Public.new(:context, :node)
# Enumerate possible mutations
# Return mutations
#
# @return [self]
# returns self if block given
#
# @return [Enumerator<Mutation>]
# returns eumerator if no block given
# @return [Enumerable<Mutation>]
#
# @api private
#
def each
return to_enum unless block_given?
yield noop_mutation
mutations.each do |mutation|
yield mutation
end
self
def mutations
mutations = []
generate_mutations(mutations)
mutations
end
# Return noop mutation
#
# @return [Mutation::Noop]
#
# @api private
#
def noop
Mutation::Neutral.new(self, node)
end
memoize :noop
memoize :mutations
# Return source path
#
@ -122,5 +102,15 @@ module Mutant
Mutation::Neutral::Noop.new(self, node)
end
# Generate mutations
#
# @param [#<<] emitter
#
# @return [undefined]
#
# @api private
#
abstract_method :generate_mutations
end # Subject
end # Mutant

View file

@ -29,13 +29,16 @@ module Mutant
# Return mutations
#
# @return [Enumerable<Mutation>]
# @param [#<<] emitter
#
# @return [undefined]
#
# @api private
#
def mutations
Mutator.each(node).map do |mutant|
Mutation::Evil.new(self, mutant)
def generate_mutations(emitter)
emitter << noop_mutation
Mutator.each(node) do |mutant|
emitter << Mutation::Evil.new(self, mutant)
end
end

View file

@ -24,4 +24,7 @@ RSpec.configure do |config|
config.include(CompressHelper)
config.include(ParserHelper)
config.include(Mutant::NodeHelpers)
config.mock_with :rspec do |config|
config.syntax = [:expect, :should]
end
end

View file

@ -8,31 +8,41 @@ describe Mutant::Runner::Config, '#subjects' do
let(:config) do
double(
'Config',
:subjects => [mutation_subject],
:class => Mutant::Config,
:subjects => [subject_a, subject_b],
:strategy => strategy,
:reporter => reporter
)
end
let(:reporter) { double('Reporter') }
let(:strategy) { double('Strategy') }
let(:mutation_subject) { double('Mutation subject') }
let(:subject_runner) { double('Subject runner') }
class DummySubjectRunner
include Concord::Public.new(:config, :mutation)
def self.run(*args); new(*args); end
end
let(:reporter) { double('Reporter') }
let(:strategy) { double('Strategy') }
let(:subject_a) { double('Subject A') }
let(:subject_b) { double('Subject B') }
let(:runner_a) { double('Runner A', :stop? => stop_a) }
let(:runner_b) { double('Runner B', :stop? => stop_b) }
before do
strategy.stub(:setup)
strategy.stub(:teardown)
reporter.stub(:report => reporter)
stub_const('Mutant::Runner::Subject', DummySubjectRunner)
Mutant::Runner.stub(:run).with(config, subject_a).and_return(runner_a)
Mutant::Runner.stub(:run).with(config, subject_b).and_return(runner_b)
end
it { should eql([DummySubjectRunner.new(config, mutation_subject)]) }
context 'without earily stop' do
let(:stop_a) { false }
let(:stop_b) { false }
it { should eql([runner_a, runner_b]) }
it_should_behave_like 'an idempotent method'
end
context 'with earily stop' do
let(:stop_a) { true }
let(:stop_b) { false }
it { should eql([runner_a]) }
it_should_behave_like 'an idempotent method'
end
it_should_behave_like 'an idempotent method'
end

View file

@ -3,50 +3,45 @@ require 'spec_helper'
describe Mutant::Runner::Config, '#success?' do
subject { object.success? }
let(:object) { described_class.run(config) }
let(:object) { described_class.new(config) }
let(:config) do
double(
'Config',
:reporter => reporter,
:strategy => strategy,
:subjects => subjects
:subjects => [subject_a, subject_b]
)
end
let(:reporter) { double('Reporter') }
let(:strategy) { double('Strategy') }
let(:subjects) { [subject_a, subject_b] }
let(:subject_a) { double('Subject A', :fails? => false) }
let(:subject_b) { double('Subject B', :fails? => false) }
class DummySubjectRunner
include Concord::Public.new(:config, :subject)
def self.run(*args)
new(*args)
end
def failed?
@subject.fails?
end
end
let(:reporter) { double('Reporter') }
let(:strategy) { double('Strategy') }
let(:subject_a) { double('Subject A') }
let(:subject_b) { double('Subject B') }
let(:runner_a) { double('Runner A', :stop? => stop_a, :success? => success_a) }
let(:runner_b) { double('Runner B', :stop? => stop_b, :success? => success_b) }
before do
stub_const('Mutant::Runner::Subject', DummySubjectRunner)
reporter.stub(:report => reporter)
strategy.stub(:setup)
strategy.stub(:teardown)
Mutant::Runner.stub(:run).with(config, subject_a).and_return(runner_a)
Mutant::Runner.stub(:run).with(config, subject_b).and_return(runner_b)
end
context 'without failed subjects' do
let(:stop_a) { false }
let(:stop_b) { false }
let(:success_a) { true }
let(:success_b) { true }
it { should be(true) }
end
context 'with failing subjects' do
before do
subject_a.stub(:fails? => true)
end
let(:stop_a) { false }
let(:stop_b) { false }
let(:success_a) { false }
let(:success_b) { true }
it { should be(false) }
end

View file

@ -1,33 +0,0 @@
require 'spec_helper'
describe Mutant::Runner, '#failed?' do
subject { object.failed? }
let(:object) { class_under_test.run(config) }
let(:config) { double('Config') }
let(:class_under_test) do
success = self.success
Class.new(described_class) do
define_method :success? do
success
end
define_method :run do
end
end
end
context 'when runner is successful' do
let(:success) { true }
it { should be(false) }
end
context 'when runner is NOT successful' do
let(:success) { false }
it { should be(true) }
end
end

View file

@ -6,15 +6,18 @@ describe Mutant::Runner::Mutation, '#killer' do
let(:config) do
double(
'Config',
:reporter => reporter,
:strategy => strategy
:fail_fast => fail_fast,
:reporter => reporter,
:strategy => strategy
)
end
let(:reporter) { double('Reporter') }
let(:mutation) { double('Mutation') }
let(:strategy) { double('Strategy') }
let(:killer) { double('Killer') }
let(:reporter) { double('Reporter') }
let(:mutation) { double('Mutation', :class => Mutant::Mutation) }
let(:strategy) { double('Strategy') }
let(:killer) { double('Killer', :success? => success) }
let(:fail_fast) { false }
let(:success) { false }
subject { object.killer }

View file

@ -3,47 +3,49 @@ require 'spec_helper'
describe Mutant::Runner::Subject, '#success?' do
subject { object.success? }
let(:object) { described_class.run(config, mutation_subject) }
let(:object) { described_class.new(config, mutation_subject) }
let(:mutation_subject) {
double(
'Subject',
:class => Mutant::Subject,
:mutations => [mutation_a, mutation_b]
)
}
let(:reporter) { double('Reporter') }
let(:mutation_subject) { double('Subject', :map => mutations) }
let(:config) { double('Config', :reporter => reporter) }
let(:mutation_a) { double('Mutation A', :failed? => false) }
let(:mutation_b) { double('Mutation B', :failed? => false) }
let(:mutations) { [mutation_a, mutation_b] }
let(:mutation_a) { double('Mutation A') }
let(:mutation_b) { double('Mutation B') }
let(:runner_a) { double('Runner A', :success? => success_a, :stop? => stop_a) }
let(:runner_b) { double('Runner B', :success? => success_b, :stop? => stop_b) }
before do
reporter.stub(:report => reporter)
Mutant::Runner.stub(:run).with(config, mutation_a).and_return(runner_a)
Mutant::Runner.stub(:run).with(config, mutation_b).and_return(runner_b)
end
class DummyMutationRunner
include Concord::Public.new(:config, :mutation)
def self.run(*args)
new(*args)
end
def failed?
@mutation.failed?
end
end
before do
stub_const('Mutant::Runner::Mutation', DummyMutationRunner)
end
context 'without evil failed mutations' do
it { should be(true) }
end
context 'with failing noop mutation' do
end
context 'with failing evil mutations' do
before do
mutation_a.stub(:failed? => true)
end
context 'with failing mutations' do
let(:stop_a) { false }
let(:stop_b) { false }
let(:success_a) { false }
let(:success_b) { true }
it { should be(false) }
it_should_behave_like 'an idempotent method'
end
context 'without failing mutations' do
let(:stop_a) { false }
let(:stop_b) { false }
let(:success_a) { true }
let(:success_b) { true }
it { should be(true) }
it_should_behave_like 'an idempotent method'
end
end

View file

@ -1,31 +0,0 @@
require 'spec_helper'
describe Mutant::Subject, '#each' do
subject { object.each { |item| yields << item } }
let(:class_under_test) do
mutations = [mutation_a, mutation_b]
Class.new(described_class) do
define_method(:mutations) { mutations }
end
end
let(:object) { class_under_test.new(context, node) }
let(:yields) { [] }
let(:node) { double('Node') }
let(:context) { double('Context') }
let(:mutant) { double('Mutant') }
let(:mutation_a) { double('Mutation A') }
let(:mutation_b) { double('Mutation B') }
it_should_behave_like 'an #each method'
let(:neutral_mutation) do
Mutant::Mutation::Neutral.new(object, node)
end
it 'yields mutations' do
expect { subject }.to change { yields.dup }.from([])
.to([neutral_mutation, mutation_a, mutation_b])
end
end

View file

@ -0,0 +1,23 @@
require 'spec_helper'
describe Mutant::Subject, '#mutations' do
subject { object.mutations }
let(:class_under_test) do
mutation_a, mutation_b = self.mutation_a, self.mutation_b
Class.new(described_class) do
define_method(:generate_mutations) do |emitter|
emitter << mutation_a
emitter << mutation_b
end
end
end
let(:object) { class_under_test.new(context, node) }
let(:node) { double('Node') }
let(:context) { double('Context') }
let(:mutation_a) { double('Mutation A') }
let(:mutation_b) { double('Mutation B') }
it { should eql([mutation_a, mutation_b]) }
end