diff --git a/lib/mutant.rb b/lib/mutant.rb index 6a554f04..71b56788 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -107,3 +107,7 @@ require 'mutant/reporter' require 'mutant/reporter/null' require 'mutant/reporter/cli' require 'mutant/reporter/cli/printer' +require 'mutant/reporter/cli/printer/config' +require 'mutant/reporter/cli/printer/subject' +require 'mutant/reporter/cli/printer/killer' +require 'mutant/reporter/cli/printer/mutation' diff --git a/lib/mutant/mutation.rb b/lib/mutant/mutation.rb index 3021660f..d8e73e6b 100644 --- a/lib/mutant/mutation.rb +++ b/lib/mutant/mutation.rb @@ -79,7 +79,7 @@ module Mutant # @api private # def source - ToSource.to_source(node) + Unparser.unparse(node) end memoize :source diff --git a/lib/mutant/reporter/cli.rb b/lib/mutant/reporter/cli.rb index a4e5c56b..eacb04db 100644 --- a/lib/mutant/reporter/cli.rb +++ b/lib/mutant/reporter/cli.rb @@ -2,40 +2,10 @@ module Mutant class Reporter # Reporter that reports in human readable format class CLI < self - include Concord::Public.new(:io) + include Concord::Public.new(:output) NL = "\n".freeze - 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 @@ -45,188 +15,10 @@ module Mutant # @api private # def report(object) - method = self.class.lookup(object) - send(method, object) + Printer.visit(object, output) self end - private - - # Report subject - # - # @param [Subject] subject - # - # @return [undefined] - # - # @api private - # - def subject(subject) - puts - puts(subject.identification) - end - # Report subject results - # - # @param [Subject] runner - # - # @return [undefined] - # - # @api private - # - def subject_results(runner) - Printer::Subject.run(io, runner) - end - - # Report mutation - # - # @param [Mutation] _mutation - # - # @return [undefined] - # - # @api private - # - def mutation(_mutation) - end - - # Report configuration - # - # @param [Mutant::Config] config - # - # @return [self] - # - # @api private - # - def config(config) - puts 'Mutant configuration:' - puts "Matcher: #{config.matcher.inspect }" - puts "Filter: #{config.filter.inspect }" - puts "Strategy: #{config.strategy.inspect}" - 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) - Printer::Config.run(io, runner) - end - - - # Report killer - # - # @param [Killer] killer - # - # @return [self] - # - # @api private - # - def killer(killer) - if killer.success? - char('.', Color::GREEN) - return - end - char('F', Color::RED) - self - end - - # Write colorized char - # - # @param [String] char - # @param [Color] - # - # @return [undefined] - # - # @api private - # - def char(char, color) - io = self.io - io.write(colorize(color, char)) - io.flush - end - - # Test for colored output - # - # @return [true] - # returns true if output is colored - # - # @return [false] - # returns false otherwise - # - # @api private - # - def color? - tty? - end - - # Colorize message - # - # @param [Color] color - # @param [String] message - # - # @api private - # - # @return [String] - # returns colorized string if color is enabled - # returns unmodified message otherwise - # - def colorize(color, message) - color = Color::NONE unless color? - color.format(message) - end - - # Write string to io - # - # @param [String] string - # - # @return [undefined] - # - # @api private - # - def puts(string=NL) - io.puts(string) - end - - # Write colorized diff - # - # @param [Mutation] mutation - # - # @return [undefined] - # - # @api private - # - def colorized_diff(mutation) - original, current = mutation.original_source, mutation.source - differ = Differ.new(original, current) - diff = color? ? differ.colorized_diff : differ.diff - - if diff.empty? - raise 'Unable to create a diff, so ast mutant or to_source does something strange!!' - end - - puts(diff) - self - end - - # Test for output to tty - # - # @return [true] - # returns true if output is a tty - # - # @return [false] - # returns false otherwise - # - # @api private - # - def tty? - @io.respond_to?(:tty?) && @io.tty? - end - memoize :tty? - end # CLI end # Reporter end # Mutant diff --git a/lib/mutant/reporter/cli/printer.rb b/lib/mutant/reporter/cli/printer.rb index 4ec4957b..2418f27c 100644 --- a/lib/mutant/reporter/cli/printer.rb +++ b/lib/mutant/reporter/cli/printer.rb @@ -2,197 +2,163 @@ module Mutant class Reporter class CLI - # CLI printer base class + # CLI runner status printer base class class Printer - include AbstractType, Adamantium::Flat, Concord.new(:output, :runner) + include AbstractType, Adamantium::Flat, Concord.new(:object, :output) - def self.run(*args) - new(*args).run + REGISTRY = {} + + # Registre handler for class + # + # @param [Class] klass + # + # @return [undefined] + # + # @api private + # + def self.handle(klass) + REGISTRY[klass] = self end - private + # Finalize CLI reporter + # + # @return [undefined] + # + # @api private + # + def self.finalize + REGISTRY.freeze + end - def puts(string = NL) - output.puts(string) + # Run printer + # + # @return [self] + # + # @api private + # + def self.run(*args) + new(*args).run + self + end + + # Visit object + # + # @param [Object] object + # @param [IO] output + # + # @return [undefined] + # + # @api private + # + def self.visit(object, output) + printer = REGISTRY.fetch(object.class) + printer.run(object, output) end abstract_method :run - # Config results printer - class Config < self + private - # Run printer - # - # @return [self] - # - # @api private - # - def run - puts "Subjects: #{subjects.length}" - puts "Mutations: #{amount_mutations}" - puts "Kills: #{amount_kills}" - puts 'Runtime: %0.2fs' % runtime - puts 'Killtime: %0.2fs' % killtime - puts 'Overhead: %0.2f%%' % overhead - puts 'Coverage: %0.2f%%' % coverage - self - end - private + # Return status color + # + # @return [Color] + # + # @api private + # + def color + success? ? Color::GREEN : Color::RED + end - # Return subjects - # - # @return [Array] - # - # @api private - # - def subjects - runner.subjects - end + # Print an info line to output + # + # @return [undefined] + # + # @api private + # + def info(string, *arguments) + puts(sprintf(string, *arguments)) + end - # Return mutations - # - # @return [Array] - # - # @api private - # - def mutations - subjects.map(&:mutations).flatten - end - memoize :mutations + # Print a status line to output + # + # @return [undefined] + # + # @api private + # + def status(string, *arguments) + puts(colorize(color, sprintf(string, *arguments))) + end - # Return amount of mutations - # - # @return [Fixnum] - # - # @api private - # - def amount_mutations - mutations.length - end + # Print a line to output + # + # @return [undefined] + # + # @api private + # + def puts(string = NL) + output.puts(string) + end - # Return amount of time in killers - # - # @return [Float] - # - # @api private - # - def killtime - mutations.map(&:runtime).inject(0, :+) - end - memoize :killtime + # Test if runner was successful + # + # @return [true] + # if runner is successful + # + # @return [false] + # otherwise + # + # @api private + # + def success? + object.success? + end - # Return amount of kills - # - # @return [Fixnum] - # - # @api private - # - def amount_kills - mutations.select(&:success?).length - end + # Test for colored output + # + # @return [true] + # returns true if output is colored + # + # @return [false] + # returns false otherwise + # + # @api private + # + def color? + tty? + end - # Return mutant overhead - # - # @return [Float] - # - # @api private - # - def overhead - (runtime - killtime) / runtime * 100 - end + # Colorize message + # + # @param [Color] color + # @param [String] message + # + # @api private + # + # @return [String] + # returns colorized string if color is enabled + # returns unmodified message otherwise + # + def colorize(color, message) + color = Color::NONE unless tty? + color.format(message) + end - # Return runtime - # - # @return [Float] - # - # @api private - # - def runtime - runner.runtime - end + # Test for output to tty + # + # @return [true] + # returns true if output is a tty + # + # @return [false] + # returns false otherwise + # + # @api private + # + def tty? + output.respond_to?(:tty?) && output.tty? + end + memoize :tty? - # Return coverage - # - # @return [Float] - # - # @api private - # - def coverage - amount_kills / amount_mutations * 100 - end - end # Config - - # Subject results printer - class Subject < self - - # Run printer - # - # @return [undefined] - # - # @api private - # - def run - puts - puts('(%02d/%02d) %3d%% - %0.02fs' % [amount_kills, amount_mutations, coverage, time]) - self - end - - private - - # Return mutation time on subject - # - # @return [Float] - # - # @api private - # - def time - mutations.map(&:runtime).inject(0, :+) - end - - # Return kills - # - # @return [Fixnum] - # - # @api private - # - def amount_kills - fails = runner.failed_mutations - fails = fails.length - amount_mutations - fails - end - - # Return amount of mutations - # - # @return [Array] - # - # @api private - # - def amount_mutations - mutations.length - end - - # Return mutations - # - # @return [Array] - # - # @api private - # - def mutations - runner.mutations - end - - # Return suject coverage - # - # @return [Float] - # - # @api private - # - def coverage - coverage = amount_kills.to_f / amount_mutations * 100 - end - - end # Subject end # Printer end # CLI end # Reporter diff --git a/lib/mutant/reporter/cli/printer/config.rb b/lib/mutant/reporter/cli/printer/config.rb new file mode 100644 index 00000000..5eed02a2 --- /dev/null +++ b/lib/mutant/reporter/cli/printer/config.rb @@ -0,0 +1,172 @@ +module Mutant + class Reporter + class CLI + class Printer + + # Printer for configuration + class Config < self + + handle(Mutant::Config) + + # Report configuration + # + # @param [Mutant::Config] config + # + # @return [self] + # + # @api private + # + def run + info 'Mutant configuration:' + info 'Matcher: %s', object.matcher.inspect + info 'Filter: %s', object.filter.inspect + info 'Strategy: %s', object.strategy.inspect + self + end + + # Config results printer + class Runner < self + + handle(Mutant::Runner::Config) + + # Run printer + # + # @return [self] + # + # @api private + # + def run + print_mutations + info 'Subjects: %s', amount_subjects + info 'Mutations: %s', amount_mutations + info 'Kills: %s', amount_kills + info 'Runtime: %0.2fs', runtime + info 'Killtime: %0.2fs', killtime + info 'Overhead: %0.2f%%', overhead + status 'Coverage: %0.2f%%', coverage + status 'Alive: %s', amount_alive + self + end + + private + + # Return subjects + # + # @return [Array] + # + # @api private + # + def subjects + object.subjects + end + + # Return amount of subjects + # + # @return [Fixnum] + # + # @api private + # + def amount_subjects + subjects.length + end + + # Print mutations + # + # @return [undefined] + # + # @api private + # + def print_mutations + object.failed_subjects.each do |subject| + Subject::Runner::Details.run(subject, output) + end + end + + # Return mutations + # + # @return [Array] + # + # @api private + # + def mutations + subjects.map(&:mutations).flatten + end + memoize :mutations + + # Return amount of mutations + # + # @return [Fixnum] + # + # @api private + # + def amount_mutations + mutations.length + end + + # Return amount of time in killers + # + # @return [Float] + # + # @api private + # + def killtime + mutations.map(&:runtime).inject(0, :+) + end + memoize :killtime + + # Return amount of kills + # + # @return [Fixnum] + # + # @api private + # + def amount_kills + mutations.select(&:success?).length + end + + # Return mutant overhead + # + # @return [Float] + # + # @api private + # + def overhead + (runtime - killtime) / runtime * 100 + end + + # Return runtime + # + # @return [Float] + # + # @api private + # + def runtime + object.runtime + end + + # Return coverage + # + # @return [Float] + # + # @api private + # + def coverage + amount_kills / amount_mutations * 100 + end + + # Return amount of alive mutations + # + # @return [Fixnum] + # + # @api private + # + def amount_alive + amount_mutations - amount_kills + end + + end # Runner + end # Config + end # Printer + end # Cli + end # Reporter +end # Mutant diff --git a/lib/mutant/reporter/cli/printer/killer.rb b/lib/mutant/reporter/cli/printer/killer.rb new file mode 100644 index 00000000..29099df9 --- /dev/null +++ b/lib/mutant/reporter/cli/printer/killer.rb @@ -0,0 +1,42 @@ +module Mutant + class Reporter + class CLI + class Printer + + # Printer for killer results + class Killer < self + + handle(Mutant::Killer::Forked) + + # Run printer + # + # @return [undefined] + # + # @api private + # + def run + if success? + char('.', Color::GREEN) + return + end + char('F', Color::RED) + end + + # Write colorized char + # + # @param [String] char + # @param [Color] + # + # @return [undefined] + # + # @api private + # + def char(char, color) + output.write(colorize(color, char)) + output.flush + end + end # Killer + end # Printer + end # CLI + end # Reporter +end # Mutant diff --git a/lib/mutant/reporter/cli/printer/mutation.rb b/lib/mutant/reporter/cli/printer/mutation.rb new file mode 100644 index 00000000..86b775fd --- /dev/null +++ b/lib/mutant/reporter/cli/printer/mutation.rb @@ -0,0 +1,55 @@ +module Mutant + class Reporter + class CLI + class Printer + # Mutation printer + class Mutation < self + + # Run mutation printer + # + # @return [undefined] + # + # @api private + # + def run + status(mutation.identification) + puts(colorized_diff) + end + + private + + # Return mutation + # + # @return [Mutation] + # + # @api private + # + def mutation + object.mutation + end + + # Return colorized diff + # + # @param [Mutation] mutation + # + # @return [undefined] + # + # @api private + # + def colorized_diff + original, current = mutation.original_source, mutation.source + differ = Differ.new(original, current) + diff = color? ? differ.colorized_diff : differ.diff + + if diff.empty? + raise 'Unable to create a diff, so ast mutant or unparser does something strange!!' + end + + diff + end + + end # Mutantion + end # Printer + end # CLI + end # Reporter +end # Mutant diff --git a/lib/mutant/reporter/cli/printer/subject.rb b/lib/mutant/reporter/cli/printer/subject.rb new file mode 100644 index 00000000..00720feb --- /dev/null +++ b/lib/mutant/reporter/cli/printer/subject.rb @@ -0,0 +1,140 @@ +module Mutant + class Reporter + class CLI + class Printer + + # Subject results printer + class Subject < self + + handle(Mutant::Subject::Method::Instance) + handle(Mutant::Subject::Method::Singleton) + + # Run subject results printer + # + # @return [undefined] + # + # @api private + # + def run + info(object.identification) + end + + class Runner < self + + handle(Mutant::Runner::Subject) + + # Run printer + # + # @return [undefined] + # + # @api private + # + def run + print_progress_bar_finish + print_stats + self + end + + private + + # Return mutation time on subject + # + # @return [Float] + # + # @api private + # + def time + mutations.map(&:runtime).inject(0, :+) + end + + # Return subject + # + # @return [Subject] + # + # @api private + # + def subject + object.subject + end + + # Print stats + # + # @return [undefned + # + # @api private + # + def print_stats + status('(%02d/%02d) %3d%% - %0.02fs', amount_kills, amount_mutations, coverage, time) + end + + # Print progress bar finish + # + # @return [undefined] + # + # @api private + # + def print_progress_bar_finish + puts unless amount_mutations.zero? + end + + # Return kills + # + # @return [Fixnum] + # + # @api private + # + def amount_kills + fails = object.failed_mutations + fails = fails.length + amount_mutations - fails + end + + # Return amount of mutations + # + # @return [Array] + # + # @api private + # + def amount_mutations + mutations.length + end + + # Return mutations + # + # @return [Array] + # + # @api private + # + def mutations + object.mutations + end + + # Return suject coverage + # + # @return [Float] + # + # @api private + # + def coverage + coverage = amount_kills.to_f / amount_mutations * 100 + end + + # Detailed subject printer + class Details < self + + def run + puts(subject.identification) + object.failed_mutations.each do |mutation| + Mutation.run(mutation, output) + end + print_stats + end + + end # Details + end # Runner + end # Subject + end # Printer + end # CLI + end # Reporter +end # Mutant + diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index ed370fbf..35132c07 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -66,12 +66,12 @@ module Mutant # Return source representation of ast # - # @return [Source] + # @return [String] # # @api private # def source - ToSource.to_source(node) + Unparser.unparse(node) end memoize :source