diff --git a/lib/mutant/reporter.rb b/lib/mutant/reporter.rb index 93c13691..b2028c51 100644 --- a/lib/mutant/reporter.rb +++ b/lib/mutant/reporter.rb @@ -1,12 +1,8 @@ module Mutant - # Abstract reporter + # Abstract base class for reporters class Reporter include Adamantium::Flat, AbstractType - ACTIONS = { - Subject => :subject, - }.freeze - # Report object # # @param [Object] object @@ -15,14 +11,7 @@ module Mutant # # @api private # - def report(object) - klass = object.class - method = self.class::ACTIONS.fetch(klass) do - raise "No reporter for: #{klass}" - end - send(method, object) - self - end + abstract_method :report end end diff --git a/lib/mutant/reporter/cli.rb b/lib/mutant/reporter/cli.rb index 514c4d7a..00c30f3f 100644 --- a/lib/mutant/reporter/cli.rb +++ b/lib/mutant/reporter/cli.rb @@ -4,17 +4,85 @@ module Mutant class CLI < self 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 # Report subject # - # @param [Subject] _subject + # @param [Subject] subject # # @return [undefined] # # @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 # Report mutation @@ -28,7 +96,7 @@ module Mutant def mutation(_mutation) end - # Report start + # Report configuration # # @param [Mutant::Config] config # @@ -45,6 +113,41 @@ module Mutant puts message.join("\n") 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 # # @param [Killer] killer @@ -54,19 +157,27 @@ module Mutant # @api private # def killer(killer) - status = killer.killed? ? 'Killed' : 'Alive' - color = killer.success? ? Color::GREEN : Color::RED - - puts(colorize(color, "%s: %s (%02.2fs)" % [status, killer.identification, killer.runtime])) - - unless killer.success? - colorized_diff(killer.mutation) + if killer.success? + char('.', Color::GREEN) + return end - + char('F', Color::RED) self 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 # diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index d8adf286..f4f860be 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -22,9 +22,22 @@ module Mutant # def initialize(config) @config = config + @start = Time.now run + @end = Time.now end + # Return runtime + # + # @return [Float] + # + # @api private + # + def runtime + @end - @start + end + memoize :runtime + # Test if runner failed # # @return [true] diff --git a/lib/mutant/runner/config.rb b/lib/mutant/runner/config.rb index de9a5e8a..1ddda1e5 100644 --- a/lib/mutant/runner/config.rb +++ b/lib/mutant/runner/config.rb @@ -56,12 +56,15 @@ module Mutant # @api private # def run + report(config) strategy = self.strategy strategy.setup @subjects = config.subjects.map do |subject| Subject.run(self, subject) end strategy.teardown + @end = Time.now + report(self) end end diff --git a/lib/mutant/runner/subject.rb b/lib/mutant/runner/subject.rb index d6e40d37..86714736 100644 --- a/lib/mutant/runner/subject.rb +++ b/lib/mutant/runner/subject.rb @@ -68,9 +68,11 @@ module Mutant # @api private # def run + report(subject) @mutations = subject.map do |mutation| Mutation.run(config, mutation) end + report(self) end end diff --git a/spec/unit/mutant/runner/config/subjects_spec.rb b/spec/unit/mutant/runner/config/subjects_spec.rb index 251843b4..f64ad8b0 100644 --- a/spec/unit/mutant/runner/config/subjects_spec.rb +++ b/spec/unit/mutant/runner/config/subjects_spec.rb @@ -5,10 +5,19 @@ describe Mutant::Runner::Config, '#subjects' do subject { object.subjects } - let(:config) { mock('Config', :subjects => [mutation_subject], :strategy => strategy) } - let(:strategy) { mock('Strategy') } - let(:mutation_subject) { mock('Mutation subject') } - let(:subject_runner) { mock('Subject runner') } + let(:config) do + mock( + 'Config', + :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 include Concord.new(:config, :mutation) @@ -19,6 +28,7 @@ describe Mutant::Runner::Config, '#subjects' do before do strategy.stub(:setup) strategy.stub(:teardown) + reporter.stub(:report => reporter) stub_const('Mutant::Runner::Subject', DummySubjectRunner) end diff --git a/spec/unit/mutant/runner/config/success_predicate_spec.rb b/spec/unit/mutant/runner/config/success_predicate_spec.rb index 0b63ba45..490622df 100644 --- a/spec/unit/mutant/runner/config/success_predicate_spec.rb +++ b/spec/unit/mutant/runner/config/success_predicate_spec.rb @@ -5,11 +5,20 @@ describe Mutant::Runner::Config, '#success?' do let(:object) { described_class.run(config) } - let(:config) { mock('Config', :strategy => strategy, :subjects => subjects) } - 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) } + let(:config) do + mock( + 'Config', + :reporter => reporter, + :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 include Concord.new(:config, :subject) @@ -25,6 +34,7 @@ describe Mutant::Runner::Config, '#success?' do before do stub_const('Mutant::Runner::Subject', DummySubjectRunner) + reporter.stub(:report => reporter) strategy.stub(:setup) strategy.stub(:teardown) end diff --git a/spec/unit/mutant/runner/subject/success_predicate_spec.rb b/spec/unit/mutant/runner/subject/success_predicate_spec.rb index 916a1ded..3ca23e0f 100644 --- a/spec/unit/mutant/runner/subject/success_predicate_spec.rb +++ b/spec/unit/mutant/runner/subject/success_predicate_spec.rb @@ -5,12 +5,17 @@ describe Mutant::Runner::Subject, '#success?' do let(:object) { described_class.run(config, mutation_subject) } + let(:reporter) { mock('Reporter') } 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_b) { mock('Mutation B', :failed? => false) } let(:mutations) { [mutation_a, mutation_b] } + before do + reporter.stub(:report => reporter) + end + class DummyMutationRunner include Concord.new(:config, :mutation)