From 8485dc313c175d68d6f9d655c4035d8807c81578 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 19 Jan 2014 00:06:40 +0100 Subject: [PATCH] Allow configurable coverage expectation --- Changelog.md | 1 + lib/mutant/cli.rb | 6 +- lib/mutant/config.rb | 3 +- lib/mutant/reporter/cli/printer.rb | 15 +++ lib/mutant/reporter/cli/printer/config.rb | 133 +++------------------- lib/mutant/runner/config.rb | 48 +++++++- lib/mutant/walker.rb | 51 +++++++++ spec/unit/mutant/runner/config_spec.rb | 58 ++++++---- 8 files changed, 174 insertions(+), 141 deletions(-) create mode 100644 lib/mutant/walker.rb diff --git a/Changelog.md b/Changelog.md index 707d23ab..1d1867d0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Changes: +* Add configurable coverage expectation via --coverage (default 100%) * Drop --rspec option use: --use rspec instead. # v0.3.4 2014-01-11 diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index f5ceab5c..ebe7c980 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -66,7 +66,7 @@ module Mutant strategy: @strategy, fail_fast: @fail_fast, reporter: reporter, - expect_coverage: @expect_coverage + expected_coverage: @expect_coverage ) end memoize :config @@ -222,7 +222,9 @@ module Mutant opts.separator '' opts.separator 'Options:' - opts.on('--use STRATEGY', 'Use STRATEGY for killing mutations') do |runner| + opts.on('--score COVERAGE', 'Fail unless COVERAGE is not reached exactly') do |coverage| + @expected_coverage = Float(coverage) + end.on('--use STRATEGY', 'Use STRATEGY for killing mutations') do |runner| use(runner) end.on('--version', 'Print mutants version') do |name| puts("mutant-#{Mutant::VERSION}") diff --git a/lib/mutant/config.rb b/lib/mutant/config.rb index 50e1c768..bfd19ced 100644 --- a/lib/mutant/config.rb +++ b/lib/mutant/config.rb @@ -11,7 +11,8 @@ module Mutant :subject_predicate, :reporter, :fail_fast, - :zombie + :zombie, + :expected_coverage ) # Enumerate subjects diff --git a/lib/mutant/reporter/cli/printer.rb b/lib/mutant/reporter/cli/printer.rb index 52bbe716..d63b98f4 100644 --- a/lib/mutant/reporter/cli/printer.rb +++ b/lib/mutant/reporter/cli/printer.rb @@ -10,6 +10,21 @@ module Mutant REGISTRY = {} + # Create delegators to object + # + # @return [undefined] + # + # @api private + # + def self.delegate(*names) + names.each do |name| + define_method(name) do + object.public_send(name) + end + private name + end + end + # Registre handler for class # # @param [Class] klass diff --git a/lib/mutant/reporter/cli/printer/config.rb b/lib/mutant/reporter/cli/printer/config.rb index 57697190..18d45050 100644 --- a/lib/mutant/reporter/cli/printer/config.rb +++ b/lib/mutant/reporter/cli/printer/config.rb @@ -10,6 +10,8 @@ module Mutant handle(Mutant::Config) + delegate :matcher, :subject_predicate, :strategy, :expected_coverage + # Report configuration # # @param [Mutant::Config] config @@ -20,9 +22,10 @@ module Mutant # def run info 'Mutant configuration:' - info 'Matcher: %s', object.matcher.inspect - info 'Subject Filter: %s', object.subject_predicate.inspect - info 'Strategy: %s', object.strategy.inspect + info 'Matcher: %s', matcher.inspect + info 'Subject Filter: %s', subject_predicate.inspect + info 'Strategy: %s', strategy.inspect + info 'Expect Coverage: %02f%%', expected_coverage.inspect self end @@ -31,6 +34,11 @@ module Mutant handle(Mutant::Runner::Config) + delegate( + :amount_kills, :amount_mutations, :amount_kils, + :coverage, :subjects, :failed_subjects, :runtime, :mutations + ) + # Run printer # # @return [self] @@ -42,73 +50,18 @@ module Mutant info 'Subjects: %s', amount_subjects info 'Mutations: %s', amount_mutations info 'Kills: %s', amount_kills + info 'Alive: %s', amount_alive 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 + status 'Expected: %0.2f%%', object.config.expected_coverage print_generic_stats self end private - # Return subjects - # - # @return [Array] - # - # @api private - # - def subjects - object.subjects - end - - # Walker for all ast nodes - class Walker - - # Run walkter - # - # @param [Parser::AST::Node] root - # - # @return [self] - # - # @api private - # - def self.run(root, &block) - new(root, block) - self - end - - private_class_method :new - - # Initialize and run walker - # - # @param [Parser::AST::Node] root - # @param [#call(node)] block - # - # @return [undefined] - # - # @api private - # - def initialize(root, block) - @root, @block = root, block - dispatch(root) - end - - private - - # Perform dispatch - # - # @return [undefined] - # - # @api private - # - def dispatch(node) - @block.call(node) - node.children.grep(Parser::AST::Node).each(&method(:dispatch)) - end - end - # Print generic stats # # @return [undefined] @@ -131,7 +84,7 @@ module Mutant # @api private # def generic_stats - object.subjects.each_with_object(Hash.new(0)) do |runner, stats| + subjects.each_with_object(Hash.new(0)) do |runner, stats| Walker.run(runner.subject.node) do |node| if Mutator::Registry.lookup(node) == Mutator::Node::Generic stats[node.type] += 1 @@ -157,32 +110,11 @@ module Mutant # @api private # def print_mutations - object.failed_subjects.each do |subject| + 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] @@ -194,16 +126,6 @@ module Mutant end memoize :killtime - # Return amount of kills - # - # @return [Fixnum] - # - # @api private - # - def amount_kills - mutations.select(&:success?).length - end - # Return mutant overhead # # @return [Float] @@ -215,27 +137,6 @@ module Mutant Rational(runtime - killtime, runtime) * 100 end - # Return runtime - # - # @return [Float] - # - # @api private - # - def runtime - object.runtime - end - - # Return coverage - # - # @return [Float] - # - # @api private - # - def coverage - return 0 if amount_mutations.zero? - Rational(amount_kills, amount_mutations) * 100 - end - # Return amount of alive mutations # # @return [Fixnum] @@ -243,12 +144,12 @@ module Mutant # @api private # def amount_alive - amount_mutations - amount_kills + object.amount_mutations - amount_kills end end # Runner end # Config end # Printer - end # Cli + end # CLI end # Reporter end # Mutant diff --git a/lib/mutant/runner/config.rb b/lib/mutant/runner/config.rb index 92e18560..f39b8fc3 100644 --- a/lib/mutant/runner/config.rb +++ b/lib/mutant/runner/config.rb @@ -40,6 +40,8 @@ module Mutant end memoize :failed_subjects + COVERAGE_PRECISION = 1 + # Test if run was successful # # @return [true] @@ -51,7 +53,7 @@ module Mutant # @api private # def success? - failed_subjects.empty? + coverage.round(COVERAGE_PRECISION) == config.expected_coverage.round(COVERAGE_PRECISION) end memoize :success? @@ -65,6 +67,50 @@ module Mutant config.strategy end + # Return coverage + # + # @return [Float] + # + # @api private + # + def coverage + return 0.0 if amount_mutations.zero? + Rational(amount_kills, amount_mutations) * 100 + end + memoize :coverage + + # Return amount of kills + # + # @return [Fixnum] + # + # @api private + # + def amount_kills + mutations.select(&:success?).length + end + memoize :amount_kills + + # 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 + private # Run config diff --git a/lib/mutant/walker.rb b/lib/mutant/walker.rb new file mode 100644 index 00000000..2e7f23c9 --- /dev/null +++ b/lib/mutant/walker.rb @@ -0,0 +1,51 @@ +module Mutant + + # Walker for all ast nodes + class Walker + + # Run walkter + # + # @param [Parser::AST::Node] root + # + # @return [self] + # + # @api private + # + def self.run(root, &block) + new(root, block) + self + end + + private_class_method :new + + # Initialize and run walker + # + # @param [Parser::AST::Node] root + # @param [#call(node)] block + # + # @return [undefined] + # + # @api private + # + def initialize(root, block) + @root, @block = root, block + dispatch(root) + end + + private + + # Perform dispatch + # + # @param [Parser::AST::Node] node + # + # @return [undefined] + # + # @api private + # + def dispatch(node) + @block.call(node) + node.children.grep(Parser::AST::Node).each(&method(:dispatch)) + end + end # Walker + +end # Mutant diff --git a/spec/unit/mutant/runner/config_spec.rb b/spec/unit/mutant/runner/config_spec.rb index ee976181..03c4c501 100644 --- a/spec/unit/mutant/runner/config_spec.rb +++ b/spec/unit/mutant/runner/config_spec.rb @@ -5,15 +5,22 @@ require 'spec_helper' describe Mutant::Runner::Config do let(:config) do - double( - 'Config', - class: Mutant::Config, - subjects: [subject_a, subject_b], - strategy: strategy, - reporter: reporter + Mutant::Config.new( + matcher: [subject_a, subject_b], + cache: Mutant::Cache.new, + debug: false, + strategy: strategy, + reporter: reporter, + fail_fast: fail_fast, + expected_coverage: expected_coverage, + zombie: false, + subject_predicate: double(:match? => false) ) end + let(:fail_fast) { false } + let(:expected_coverage) { 100.0 } + before do reporter.stub(report: reporter) strategy.stub(:setup) @@ -59,30 +66,39 @@ describe Mutant::Runner::Config do let(:object) { described_class.new(config) } + let(:mutation_a) do + double('Mutation A', success?: false) + end + + let(:mutation_b) do + double('Mutation B', success?: true) + end + let(:runner_a) do - double('Runner A', stop?: stop_a, success?: success_a) + double('Runner A', stop?: false, success?: false, mutations: [mutation_a]) end let(:runner_b) do - double('Runner B', stop?: stop_b, success?: success_b) + double('Runner B', stop?: false, success?: true, mutations: [mutation_b]) end - context 'without failed subjects' do - let(:stop_a) { false } - let(:stop_b) { false } - let(:success_a) { true } - let(:success_b) { true } + context 'without fail fast' do - it { should be(true) } - end + context 'when expected coverage equals actual coverage' do + let(:expected_coverage) { 50.0 } + it { should be(true) } + end - context 'with failing subjects' do - let(:stop_a) { false } - let(:stop_b) { false } - let(:success_a) { false } - let(:success_b) { true } + context 'when expected coverage closely equals actual coverage' do + let(:expected_coverage) { 50.01 } + it { should be(true) } + end + + context 'when expected coverage does not equal actual coverage' do + let(:expected_coverage) { 51.00 } + it { should be(false) } + end - it { should be(false) } end end end