Finish cli reporter refactoring

Readds mutation reporting
This commit is contained in:
Markus Schirp 2013-06-21 23:52:57 +02:00
parent 20418d0969
commit dbc6455ae1
9 changed files with 556 additions and 385 deletions

View file

@ -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'

View file

@ -79,7 +79,7 @@ module Mutant
# @api private
#
def source
ToSource.to_source(node)
Unparser.unparse(node)
end
memoize :source

View file

@ -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

View file

@ -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<Subject>]
#
# @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<Mutation>]
#
# @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<Mutation>]
#
# @api private
#
def amount_mutations
mutations.length
end
# Return mutations
#
# @return [Array<Mutation>]
#
# @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

View file

@ -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<Subject>]
#
# @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<Mutation>]
#
# @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

View file

@ -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

View file

@ -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

View file

@ -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<Mutation>]
#
# @api private
#
def amount_mutations
mutations.length
end
# Return mutations
#
# @return [Array<Mutation>]
#
# @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

View file

@ -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