Connect reporters with runners

This fixes all unit and integration specs!
This commit is contained in:
Markus Schirp 2013-04-21 02:20:18 +02:00
parent c76b7c05c5
commit f2a6243ac0
8 changed files with 178 additions and 35 deletions

View file

@ -1,12 +1,8 @@
module Mutant module Mutant
# Abstract reporter # Abstract base class for reporters
class Reporter class Reporter
include Adamantium::Flat, AbstractType include Adamantium::Flat, AbstractType
ACTIONS = {
Subject => :subject,
}.freeze
# Report object # Report object
# #
# @param [Object] object # @param [Object] object
@ -15,14 +11,7 @@ module Mutant
# #
# @api private # @api private
# #
def report(object) abstract_method :report
klass = object.class
method = self.class::ACTIONS.fetch(klass) do
raise "No reporter for: #{klass}"
end
send(method, object)
self
end
end end
end end

View file

@ -4,17 +4,85 @@ module Mutant
class CLI < self class CLI < self
include Concord.new(:io) include Concord.new(:io)
ACTIONS = {
Config => :config,
Subject => :subject,
Killer => :killer,
Runner::Subject => :subject_results,
Runner::Config => :config_results
}.freeze
# Perform lookup
#
# @param [Object] object
#
# @return [Symbol]
# if found
#
# @raise [RuntimeError]
# otherwise
#
# @api private
#
def self.lookup(object)
current = klass = object.class
begin
symbol = ACTIONS[current]
return symbol if symbol
current = current.superclass
end while current < ::Object
raise "No reporter for #{klass}"
end
# Report object
#
# @param [Object] object
#
# @return [self]
#
# @api private
#
def report(object)
method = self.class.lookup(object)
send(method, object)
self
end
private private
# Report subject # Report subject
# #
# @param [Subject] _subject # @param [Subject] subject
# #
# @return [undefined] # @return [undefined]
# #
# @api private # @api private
# #
def subject(_subject) def subject(subject)
puts
puts(subject.identification)
end
# Report subject results
#
# @param [Subject]
#
# @return [undefined]
#
# @api private
#
def subject_results(runner)
mutations = runner.mutations
if mutations.any?
puts # There is an open line
end
time = mutations.map(&:runtime).inject(0, :+)
mutations = mutations.length
fails = runner.failed_mutations
fails = fails.length
kills = mutations - fails
coverage = kills.to_f / mutations * 100
puts('(%02d/%02d) %3d%% - %0.02fs' % [kills, mutations, coverage, time])
end end
# Report mutation # Report mutation
@ -28,7 +96,7 @@ module Mutant
def mutation(_mutation) def mutation(_mutation)
end end
# Report start # Report configuration
# #
# @param [Mutant::Config] config # @param [Mutant::Config] config
# #
@ -45,6 +113,41 @@ module Mutant
puts message.join("\n") puts message.join("\n")
end end
# Report configuration results
#
# TODO: Break this stuff into smaller methods or factor out in a subclass
#
# @param [Reporter::Config] runner
#
# @return [self]
#
# @api private
#
def config_results(runner)
message = []
subjects = runner.subjects
mutations = subjects.map(&:mutations).flatten
killtime = mutations.map(&:runtime).inject(0, :+)
kills = mutations.select(&:success?)
subjects = subjects.length
mutations = mutations.length
kills = kills.length
coverage = kills.to_f / mutations * 100
runtime = runner.runtime
overhead = (runtime - killtime) / runtime * 100
puts "Subjects: #{subjects}"
puts "Mutations: #{mutations}"
puts "Kills: #{kills}"
puts 'Runtime: %0.2fs' % runner.runtime
puts 'Killtime: %0.2fs' % killtime
puts 'Overhead: %0.2f%%' % overhead
puts 'Coverage: %0.2f%%' % coverage
end
# Report killer # Report killer
# #
# @param [Killer] killer # @param [Killer] killer
@ -54,19 +157,27 @@ module Mutant
# @api private # @api private
# #
def killer(killer) def killer(killer)
status = killer.killed? ? 'Killed' : 'Alive' if killer.success?
color = killer.success? ? Color::GREEN : Color::RED char('.', Color::GREEN)
return
puts(colorize(color, "%s: %s (%02.2fs)" % [status, killer.identification, killer.runtime]))
unless killer.success?
colorized_diff(killer.mutation)
end end
char('F', Color::RED)
self self
end end
private # Write colorized char
#
# @param [String] char
# @param [Color]
#
# @return [undefined]
#
# @api private
#
def char(char, color)
io.write(colorize(color, char))
io.flush
end
# Test for colored output # Test for colored output
# #

View file

@ -22,9 +22,22 @@ module Mutant
# #
def initialize(config) def initialize(config)
@config = config @config = config
@start = Time.now
run run
@end = Time.now
end end
# Return runtime
#
# @return [Float]
#
# @api private
#
def runtime
@end - @start
end
memoize :runtime
# Test if runner failed # Test if runner failed
# #
# @return [true] # @return [true]

View file

@ -56,12 +56,15 @@ module Mutant
# @api private # @api private
# #
def run def run
report(config)
strategy = self.strategy strategy = self.strategy
strategy.setup strategy.setup
@subjects = config.subjects.map do |subject| @subjects = config.subjects.map do |subject|
Subject.run(self, subject) Subject.run(self, subject)
end end
strategy.teardown strategy.teardown
@end = Time.now
report(self)
end end
end end

View file

@ -68,9 +68,11 @@ module Mutant
# @api private # @api private
# #
def run def run
report(subject)
@mutations = subject.map do |mutation| @mutations = subject.map do |mutation|
Mutation.run(config, mutation) Mutation.run(config, mutation)
end end
report(self)
end end
end end

View file

@ -5,10 +5,19 @@ describe Mutant::Runner::Config, '#subjects' do
subject { object.subjects } subject { object.subjects }
let(:config) { mock('Config', :subjects => [mutation_subject], :strategy => strategy) } let(:config) do
let(:strategy) { mock('Strategy') } mock(
let(:mutation_subject) { mock('Mutation subject') } 'Config',
let(:subject_runner) { mock('Subject runner') } :subjects => [mutation_subject],
:strategy => strategy,
:reporter => reporter
)
end
let(:reporter) { mock('Reporter') }
let(:strategy) { mock('Strategy') }
let(:mutation_subject) { mock('Mutation subject') }
let(:subject_runner) { mock('Subject runner') }
class DummySubjectRunner class DummySubjectRunner
include Concord.new(:config, :mutation) include Concord.new(:config, :mutation)
@ -19,6 +28,7 @@ describe Mutant::Runner::Config, '#subjects' do
before do before do
strategy.stub(:setup) strategy.stub(:setup)
strategy.stub(:teardown) strategy.stub(:teardown)
reporter.stub(:report => reporter)
stub_const('Mutant::Runner::Subject', DummySubjectRunner) stub_const('Mutant::Runner::Subject', DummySubjectRunner)
end end

View file

@ -5,11 +5,20 @@ describe Mutant::Runner::Config, '#success?' do
let(:object) { described_class.run(config) } let(:object) { described_class.run(config) }
let(:config) { mock('Config', :strategy => strategy, :subjects => subjects) } let(:config) do
let(:strategy) { mock('Strategy') } mock(
let(:subjects) { [subject_a, subject_b] } 'Config',
let(:subject_a) { mock('Subject A', :fails? => false) } :reporter => reporter,
let(:subject_b) { mock('Subject B', :fails? => false) } :strategy => strategy,
:subjects => subjects
)
end
let(:reporter) { mock('Reporter') }
let(:strategy) { mock('Strategy') }
let(:subjects) { [subject_a, subject_b] }
let(:subject_a) { mock('Subject A', :fails? => false) }
let(:subject_b) { mock('Subject B', :fails? => false) }
class DummySubjectRunner class DummySubjectRunner
include Concord.new(:config, :subject) include Concord.new(:config, :subject)
@ -25,6 +34,7 @@ describe Mutant::Runner::Config, '#success?' do
before do before do
stub_const('Mutant::Runner::Subject', DummySubjectRunner) stub_const('Mutant::Runner::Subject', DummySubjectRunner)
reporter.stub(:report => reporter)
strategy.stub(:setup) strategy.stub(:setup)
strategy.stub(:teardown) strategy.stub(:teardown)
end end

View file

@ -5,12 +5,17 @@ describe Mutant::Runner::Subject, '#success?' do
let(:object) { described_class.run(config, mutation_subject) } let(:object) { described_class.run(config, mutation_subject) }
let(:reporter) { mock('Reporter') }
let(:mutation_subject) { mock('Subject', :map => mutations) } let(:mutation_subject) { mock('Subject', :map => mutations) }
let(:config) { mock('Config') } let(:config) { mock('Config', :reporter => reporter) }
let(:mutation_a) { mock('Mutation A', :failed? => false) } let(:mutation_a) { mock('Mutation A', :failed? => false) }
let(:mutation_b) { mock('Mutation B', :failed? => false) } let(:mutation_b) { mock('Mutation B', :failed? => false) }
let(:mutations) { [mutation_a, mutation_b] } let(:mutations) { [mutation_a, mutation_b] }
before do
reporter.stub(:report => reporter)
end
class DummyMutationRunner class DummyMutationRunner
include Concord.new(:config, :mutation) include Concord.new(:config, :mutation)