free_mutant/lib/mutant/reporter/cli/printer.rb

624 lines
15 KiB
Ruby
Raw Normal View History

module Mutant
class Reporter
class CLI
# CLI runner status printer base class
class Printer
include AbstractType, Delegator, Adamantium::Flat, Concord.new(:output, :object)
2014-10-23 11:37:53 +00:00
delegate(:success?)
NL = "\n".freeze
# Run printer on object to output
#
# @param [IO] output
# @param [Object] object
#
# @return [self]
#
2014-06-28 20:52:47 +00:00
# @api private
#
def self.run(output, object)
2014-07-17 13:59:25 +00:00
new(output, object).run
end
# Run printer
#
# @return [self]
#
# @api private
#
abstract_method :run
private
# Return status color
#
# @return [Color]
#
# @api private
#
2014-05-11 12:39:12 +00:00
def status_color
success? ? Color::GREEN : Color::RED
end
# Visit a collection of objects
#
2014-07-17 13:59:25 +00:00
# @return [Class::Printer] printer
# @return [Enumerable<Object>] collection
#
# @return [undefined]
#
# @api private
#
2014-07-17 13:59:25 +00:00
def visit_collection(printer, collection)
collection.each do |object|
visit(printer, object)
end
end
# Visit object
#
2014-07-17 13:59:25 +00:00
# @param [Class::Printer] printer
# @param [Object] object
#
# @return [undefined]
#
# @api private
#
2014-07-17 13:59:25 +00:00
def visit(printer, object)
printer.run(output, object)
end
# Print an info line to output
#
# @return [undefined]
#
# @api private
#
def info(string, *arguments)
2014-05-27 15:12:36 +00:00
puts(format(string, *arguments))
end
# Print a status line to output
#
# @return [undefined]
#
# @api private
#
def status(string, *arguments)
2014-05-27 15:12:36 +00:00
puts(colorize(status_color, format(string, *arguments)))
end
# Print a line to output
#
# @return [undefined]
#
# @api private
#
2014-07-17 13:59:25 +00:00
def puts(string)
output.puts(string)
end
# Colorize message
#
# @param [Color] color
# @param [String] message
#
# @api private
#
# @return [String]
# if color is enabled
# unmodified message otherwise
#
def colorize(color, message)
color = Color::NONE unless tty?
color.format(message)
end
2014-07-17 13:59:25 +00:00
# Test if output is a tty
#
2014-06-15 19:27:57 +00:00
# @return [Boolean]
#
# @api private
#
def tty?
2014-07-17 13:59:25 +00:00
output.tty?
end
2014-07-17 13:59:25 +00:00
# Test if output can be colored
#
# @return [Boolean]
#
# @api private
#
alias_method :color?, :tty?
2014-10-23 11:37:53 +00:00
# Printer for runner status
class Status < self
2014-12-09 00:10:31 +00:00
delegate(:active_jobs, :payload)
2014-07-17 13:59:25 +00:00
# Print progress for collector
#
# @return [self]
#
# @api private
#
def run
2014-12-09 00:10:31 +00:00
visit(EnvProgress, payload)
job_status
info('Active subjects: %d', active_subject_results.length)
2014-07-17 13:59:25 +00:00
visit_collection(SubjectProgress, active_subject_results)
self
end
2014-10-23 11:37:53 +00:00
private
# Print worker status
#
# @return [undefined]
#
2014-12-01 18:33:47 +00:00
# @api private
#
2014-10-23 11:37:53 +00:00
def job_status
return if active_jobs.empty?
info('Active Jobs:')
2014-12-09 00:10:31 +00:00
active_jobs.sort_by(&:index).each do |job|
info('%d: %s', job.index, job.payload.identification)
2014-10-23 11:37:53 +00:00
end
end
# Return active subject results
#
# @return [Array<Result::Subject>]
#
# @api private
#
def active_subject_results
2015-05-02 23:11:44 +00:00
active_mutation_jobs = active_jobs.select { |job| job.payload.kind_of?(Mutation) }
2014-12-09 00:10:31 +00:00
active_subjects = active_mutation_jobs.map(&:payload).flat_map(&:subject).to_set
2014-10-23 11:37:53 +00:00
2014-12-09 00:10:31 +00:00
payload.subject_results.select do |subject_result|
2014-10-23 11:37:53 +00:00
active_subjects.include?(subject_result.subject)
end
end
end # Status
2014-07-17 13:59:25 +00:00
# Progress printer for configuration
class Config < self
# Report configuration
#
# @param [Mutant::Config] config
#
# @return [self]
#
# @api private
#
2014-12-22 01:28:30 +00:00
# rubocop:disable AbcSize
#
2014-07-17 13:59:25 +00:00
def run
info 'Mutant configuration:'
info 'Matcher: %s', object.matcher.inspect
info 'Integration: %s', object.integration.name
2015-03-06 04:46:03 +00:00
info 'Expect Coverage: %0.2f%%', (object.expected_coverage * 100)
info 'Jobs: %d', object.jobs
info 'Includes: %s', object.includes.inspect
info 'Requires: %s', object.requires.inspect
2014-07-17 13:59:25 +00:00
self
end
end # Config
# Env progress printer
class EnvProgress < self
delegate(
:coverage,
:amount_subjects,
:amount_mutations,
:amount_mutations_alive,
:amount_mutations_killed,
:runtime,
:killtime,
:overhead,
:env
)
# Run printer
#
# @return [self]
#
# @api private
#
2014-12-22 01:28:30 +00:00
# rubocop:disable MethodLength
#
2014-07-17 13:59:25 +00:00
def run
visit(Config, env.config)
info 'Subjects: %s', amount_subjects
info 'Mutations: %s', amount_mutations
info 'Kills: %s', amount_mutations_killed
info 'Alive: %s', amount_mutations_alive
info 'Runtime: %0.2fs', runtime
info 'Killtime: %0.2fs', killtime
info 'Overhead: %0.2f%%', overhead_percent
status 'Coverage: %0.2f%%', coverage_percent
2015-03-06 04:46:03 +00:00
status 'Expected: %0.2f%%', (env.config.expected_coverage * 100)
2014-07-17 13:59:25 +00:00
self
end
private
# Return coverage percent
#
# @return [Float]
#
# @api private
#
def coverage_percent
coverage * 100
2014-07-17 13:59:25 +00:00
end
# Return overhead percent
#
# @return [Float]
#
# @api private
#
def overhead_percent
(overhead / killtime) * 100
end
2014-07-17 13:59:25 +00:00
end # EnvProgress
# Full env result reporter
class EnvResult < self
delegate(:failed_subject_results)
# Run printer
#
# @return [self]
#
# @api private
#
def run
visit_collection(SubjectResult, failed_subject_results)
visit(EnvProgress, object)
self
end
end # EnvResult
# Subject report printer
class SubjectResult < self
delegate :subject, :failed_mutations, :tests
2014-07-17 13:59:25 +00:00
# Run report printer
#
# @return [self]
#
# @api private
#
def run
status(subject.identification)
tests.each do |test|
2014-07-17 13:59:25 +00:00
puts("- #{test.identification}")
end
visit_collection(MutationResult, object.alive_mutation_results)
self
end
end # Subject
# Printer for mutation progress results
class MutationProgressResult < self
SUCCESS = '.'.freeze
FAILURE = 'F'.freeze
# Run printer
#
# @return [self]
#
# @api private
#
def run
char(success? ? SUCCESS : FAILURE)
end
private
# Write colorized char
#
# @param [String] char
#
# @return [undefined]
#
# @api private
#
def char(char)
output.write(colorize(status_color, char))
end
end # MutationProgressResult
2014-12-08 18:13:49 +00:00
# Reporter for progressive output format on scheduler Status objects
class StatusProgressive < self
FORMAT = '(%02d/%02d) %3d%% - killtime: %0.02fs runtime: %0.02fs overhead: %0.02fs'.freeze
delegate(
:coverage,
:runtime,
:amount_mutations_killed,
:amount_mutations,
:amount_mutation_results,
:killtime,
:overhead
)
# Run printer
#
# @return [self]
#
# @api private
#
def run
status(
FORMAT,
amount_mutations_killed,
amount_mutations,
coverage * 100,
killtime,
runtime,
overhead
)
self
end
private
# Return object being printed
#
# @return [Result::Env]
#
# @api private
#
def object
2014-12-09 00:10:31 +00:00
super().payload
2014-12-08 18:13:49 +00:00
end
end
2014-07-17 13:59:25 +00:00
# Reporter for subject progress
class SubjectProgress < self
FORMAT = '(%02d/%02d) %3d%% - killtime: %0.02fs runtime: %0.02fs overhead: %0.02fs'.freeze
delegate(
2014-12-22 18:04:17 +00:00
:tests,
2014-07-17 13:59:25 +00:00
:subject,
:coverage,
:runtime,
:amount_mutations_killed,
:amount_mutations,
:amount_mutation_results,
:killtime,
:overhead
)
# Run printer
#
# @return [self]
#
# @api private
#
def run
puts("#{subject.identification} mutations: #{amount_mutations}")
print_mutation_results
print_progress_bar_finish
print_stats
print_tests
2014-07-17 13:59:25 +00:00
self
end
private
# Print stats
#
# @return [undefined]
#
# @api private
#
def print_stats
status(
FORMAT,
amount_mutations_killed,
amount_mutations,
coverage * 100,
killtime,
runtime,
overhead
)
end
# Print tests
#
# @return [undefined]
#
# @api private
#
def print_tests
2014-12-22 18:04:17 +00:00
tests.each do |test|
2014-07-17 13:59:25 +00:00
puts "- #{test.identification}"
end
end
# Print progress bar finish
#
# @return [undefined]
#
# @api private
#
def print_progress_bar_finish
puts(NL) unless amount_mutation_results.zero?
end
# Print mutation results
#
# @return [undefined]
#
# @api private
#
def print_mutation_results
visit_collection(MutationProgressResult, object.mutation_results)
2014-07-17 13:59:25 +00:00
end
end # Subject
# Reporter for mutation results
class MutationResult < self
delegate :mutation, :test_result
2014-07-17 13:59:25 +00:00
2014-12-22 01:28:30 +00:00
DIFF_ERROR_MESSAGE =
'BUG: Mutation NOT resulted in exactly one diff hunk. Please report a reproduction!'.freeze
2014-07-17 13:59:25 +00:00
MAP = {
Mutant::Mutation::Evil => :evil_details,
Mutant::Mutation::Neutral => :neutral_details,
Mutant::Mutation::Noop => :noop_details
}.freeze
NEUTRAL_MESSAGE =
"--- Neutral failure ---\n" \
"Original code was inserted unmutated. And the test did NOT PASS.\n" \
"Your tests do not pass initially or you found a bug in mutant / unparser.\n" \
"Subject AST:\n" \
"%s\n" \
"Unparsed Source:\n" \
"%s\n" \
"Test Result:\n".freeze
2014-07-17 13:59:25 +00:00
NOOP_MESSAGE =
"---- Noop failure -----\n" \
"No code was inserted. And the test did NOT PASS.\n" \
"This is typically a problem of your specs not passing unmutated.\n" \
"Test Result:\n".freeze
2014-07-17 13:59:25 +00:00
FOOTER = '-----------------------'.freeze
# Run report printer
#
# @return [self]
#
# @api private
#
def run
puts(mutation.identification)
print_details
puts(FOOTER)
self
end
private
# Return details
#
# @return [undefined]
#
# @api private
#
def print_details
send(MAP.fetch(mutation.class))
end
# Return evil details
#
# @return [String]
#
# @api private
#
def evil_details
original, current = mutation.original_source, mutation.source
diff = Mutant::Diff.build(original, current)
diff = color? ? diff.colorized_diff : diff.diff
2014-08-05 08:09:53 -07:00
puts(diff || ['Original source:', original, 'Mutated Source:', current, DIFF_ERROR_MESSAGE])
2014-07-17 13:59:25 +00:00
end
# Noop details
#
# @return [String]
#
# @api private
#
def noop_details
info(NOOP_MESSAGE)
visit_test_result
2014-07-17 13:59:25 +00:00
end
# Neutral details
#
# @return [String]
#
# @api private
#
def neutral_details
info(NEUTRAL_MESSAGE, mutation.subject.node.inspect, mutation.source)
visit_test_result
2014-07-17 13:59:25 +00:00
end
# Visit failed test results
#
# @return [undefined]
#
# @api private
#
def visit_test_result
visit(TestResult, test_result)
2014-07-17 13:59:25 +00:00
end
end # MutationResult
# Test result reporter
class TestResult < self
delegate :tests, :runtime
2014-07-17 13:59:25 +00:00
# Run test result reporter
#
# @return [self]
#
# @api private
#
def run
status('- %d @ runtime: %s', tests.length, runtime)
tests.each do |test|
puts(" - #{test.identification}")
end
2014-07-17 13:59:25 +00:00
puts('Test Output:')
puts(object.output)
end
2014-12-01 18:33:47 +00:00
# Test if test result is successful
#
# Only used to determine color.
#
# @return [false]
#
# @api private
#
2014-10-23 11:37:53 +00:00
def success?
false
end
2014-07-17 13:59:25 +00:00
end # TestResult
end # Printer
end # CLI
end # Reporter
end # Mutant