lib/mutant/reporter/cli/printer.rb in mutant-0.5.26 vs lib/mutant/reporter/cli/printer.rb in mutant-0.6.0

- old
+ new

@@ -1,9 +1,8 @@ module Mutant class Reporter class CLI - # CLI runner status printer base class class Printer include AbstractType, Delegator, Adamantium::Flat, Concord.new(:output, :object) NL = "\n".freeze @@ -16,13 +15,11 @@ # @return [self] # # @api private # def self.run(output, object) - handler = lookup(object.class) - handler.new(output, object).run - self + new(output, object).run end # Run printer # # @return [self] @@ -43,30 +40,34 @@ success? ? Color::GREEN : Color::RED end # Visit a collection of objects # + # @return [Class::Printer] printer # @return [Enumerable<Object>] collection # # @return [undefined] # # @api private # - def visit_collection(collection) - collection.each(&method(:visit)) + def visit_collection(printer, collection) + collection.each do |object| + visit(printer, object) + end end # Visit object # + # @param [Class::Printer] printer # @param [Object] object # # @return [undefined] # # @api private # - def visit(object) - self.class.run(output, object) + def visit(printer, object) + printer.run(output, object) end # Print an info line to output # # @return [undefined] @@ -91,11 +92,11 @@ # # @return [undefined] # # @api private # - def puts(string = NL) + def puts(string) output.puts(string) end # Test if runner was successful # @@ -105,20 +106,10 @@ # def success? object.success? end - # Test if output can be colored - # - # @return [Boolean] - # - # @api private - # - def color? - tty? - end - # Colorize message # # @param [Color] color # @param [String] message # @@ -131,20 +122,409 @@ def colorize(color, message) color = Color::NONE unless tty? color.format(message) end - # Test for output to tty + # Test if output is a tty # # @return [Boolean] # # @api private # def tty? - output.respond_to?(:tty?) && output.tty? + output.tty? end - memoize :tty? + # Test if output can be colored + # + # @return [Boolean] + # + # @api private + # + alias_method :color?, :tty? + + # Printer for run collector + class Collector < self + + # Print progress for collector + # + # @return [self] + # + # @api private + # + def run + visit(EnvProgress, object.result) + active_subject_results = object.active_subject_results + info('Active subjects: %d', active_subject_results.length) + visit_collection(SubjectProgress, active_subject_results) + self + end + + end # Collector + + # Progress printer for configuration + class Config < self + + # Report configuration + # + # @param [Mutant::Config] config + # + # @return [self] + # + # @api private + # + def run + info 'Mutant configuration:' + info 'Matcher: %s', object.matcher_config.inspect + info 'Integration: %s', object.integration.name + info 'Expect Coverage: %0.2f%%', object.expected_coverage.inspect + info 'Processes: %d', object.processes + info 'Includes: %s', object.includes.inspect + info 'Requires: %s', object.requires.inspect + 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 + # + def run + visit(Config, env.config) + info 'Available Subjects: %s', amount_subjects + 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 + status 'Expected: %0.2f%%', env.config.expected_coverage + self + end + + private + + # Return coverage percent + # + # @return [Float] + # + # @api private + # + def coverage_percent + coverage * 100 + end + + # Return overhead percent + # + # @return [Float] + # + # @api private + # + def overhead_percent + (overhead / killtime) * 100 + end + + 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 + + # Run report printer + # + # @return [self] + # + # @api private + # + def run + status(subject.identification) + subject.tests.each do |test| + 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 + + # Reporter for subject progress + class SubjectProgress < self + + FORMAT = '(%02d/%02d) %3d%% - killtime: %0.02fs runtime: %0.02fs overhead: %0.02fs'.freeze + + delegate( + :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_tests + print_mutation_results + print_progress_bar_finish + print_stats + 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 + subject.tests.each do |test| + 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) + end + + end # Subject + + # Reporter for mutation results + class MutationResult < self + + delegate :mutation, :failed_test_results + + DIFF_ERROR_MESSAGE = 'BUG: Mutation NOT resulted in exactly one diff. Please report a reproduction!'.freeze + + 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 Reports: %d\n" + + 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 Reports: %d\n" + + 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 + puts(diff || ['Original source:', original, 'Mutated Source:', current, DIFF_ERROR_MESSAGE]) + end + + # Noop details + # + # @return [String] + # + # @api private + # + def noop_details + info(NOOP_MESSAGE, failed_test_results.length) + visit_failed_test_results + end + + # Neutral details + # + # @return [String] + # + # @api private + # + def neutral_details + info(NEUTRAL_MESSAGE, mutation.subject.node.inspect, mutation.source, failed_test_results.length) + visit_failed_test_results + end + + # Visit failed test results + # + # @return [undefined] + # + # @api private + # + def visit_failed_test_results + visit_collection(TestResult, failed_test_results) + end + + end # MutationResult + + # Test result reporter + class TestResult < self + + delegate :test, :runtime + + # Run test result reporter + # + # @return [self] + # + # @api private + # + def run + status('- %s / runtime: %s', test.identification, object.runtime) + puts('Test Output:') + puts(object.output) + end + + end # TestResult end # Printer end # CLI end # Reporter end # Mutant