From dffe2a26e6470a919d69a328412e29bf879059cd Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Thu, 22 Nov 2012 00:10:50 +0100 Subject: [PATCH] Add strategy objects --- Gemfile | 1 + lib/mutant.rb | 2 + lib/mutant/cli.rb | 78 ++++++++++++----- lib/mutant/context/scope.rb | 2 +- lib/mutant/killer.rb | 27 +++++- lib/mutant/killer/forking.rb | 52 +++++------ lib/mutant/killer/rspec.rb | 2 +- lib/mutant/loader.rb | 1 + lib/mutant/reporter/cli.rb | 6 +- lib/mutant/runner.rb | 2 +- lib/mutant/strategy.rb | 45 ++++++++++ lib/mutant/subject.rb | 2 +- locator.rb | 87 +++++++++++++++++++ .../unit/mutant/cli/class_methods/new_spec.rb | 7 +- .../mutant/killer/class_methods/run_spec.rb | 6 +- spec/unit/mutant/killer/fail_ques_spec.rb | 6 +- .../mutant/mutator/literal/symbol_spec.rb | 2 +- 17 files changed, 264 insertions(+), 64 deletions(-) create mode 100644 lib/mutant/strategy.rb create mode 100644 locator.rb diff --git a/Gemfile b/Gemfile index 5cc2992a..b194b5ea 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem 'abstract_class', :git => 'https://github.com/dkubb/abstract_class.git' gem 'equalizer', :git => 'https://github.com/dkubb/equalizer.git' gem 'to_source', :git => 'https://github.com/mbj/to_source.git' gem 'melbourne', :git => 'https://github.com/mbj/melbourne.git' +gem 'inflector', :git => 'https://github.com/mbj/inflector.git' group :development do gem 'rake', '~> 0.9.2' diff --git a/lib/mutant.rb b/lib/mutant.rb index bbf5455a..fccb8010 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -7,6 +7,7 @@ require 'securerandom' require 'equalizer' require 'digest/sha1' require 'to_source' +require 'inflector' require 'ice_nine' require 'ice_nine/core_ext/object' require 'diff/lcs' @@ -83,6 +84,7 @@ require 'mutant/matcher/scope_methods' require 'mutant/killer' require 'mutant/killer/rspec' require 'mutant/killer/forking' +require 'mutant/strategy' require 'mutant/runner' require 'mutant/cli' require 'mutant/color' diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index fe38add7..cb0e9df4 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -59,21 +59,15 @@ module Mutant end memoize :filter - # Return killer + # Return stratety # - # @return [Mutant::Killer] + # @return [Strategy] # # @api private # - def killer - killer = Mutant::Killer::Rspec - if @forking - Mutant::Killer::Forking.new(killer) - else - killer - end + def strategy + @strategy || raise(Error, 'no strategy was set!') end - memoize :killer # Return reporter # @@ -89,15 +83,32 @@ module Mutant private OPTIONS = { - '--code' => [:add_filter, Mutation::Filter::Code], - '-I' => [:add_load_path], - '--include' => [:add_load_path], - '-r' => [:require_library], - '--require' => [:require_library], - '--fork' => [:set_forking] + '--code' => [:add_filter, Mutation::Filter::Code ], + '-I' => [:add_load_path ], + '--include' => [:add_load_path ], + '-r' => [:require_library ], + '--require' => [:require_library ], + #'--killer-fork' => [:enable_killer_fork ], + '--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ], + '--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ] }.deep_freeze - OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9]+\z).freeze + OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze + + # Return selected killer + # + # @return [Killer] + # + # @api private + # + def selected_killer + unless @rspec + raise Error, "Only rspec is supported currently use --rspec switch" + end + + Mutant::Killer::Rspec + end + memoize :selected_killer # Return option for argument with index # @@ -128,6 +139,7 @@ module Mutant dispatch end + strategy matcher end @@ -163,7 +175,7 @@ module Mutant # @api private # def dispatch - if OPTION_PATTERN.match(current_argument) + if OPTION_PATTERN =~ current_argument dispatch_option else dispatch_matcher @@ -240,7 +252,7 @@ module Mutant consume(2) end - # Set forking + # Enable rspec # # @api private # @@ -248,11 +260,37 @@ module Mutant # # @api private # - def set_forking + def enable_rspec + consume(1) + @rspec = true + end + + # Enable killer forking + # + # @api private + # + # @return [self] + # + # @api private + # + def enable_killer_fork consume(1) @forking = true end + # Set strategy + # + # @param [Strategy] + # + # @api private + # + # @return [undefined] + # + def set_strategy(strategy) + consume(1) + @strategy = strategy + end + # Require library # # @api private diff --git a/lib/mutant/context/scope.rb b/lib/mutant/context/scope.rb index d99d587e..0275e8e3 100644 --- a/lib/mutant/context/scope.rb +++ b/lib/mutant/context/scope.rb @@ -2,7 +2,7 @@ module Mutant class Context # Scope context for mutation (Class or Module) class Scope < self - include Adamantium::Flat, AbstractClass + include Adamantium::Flat, AbstractClass, Equalizer.new(:scope, :source_path) # Class context for mutation class Class < self diff --git a/lib/mutant/killer.rb b/lib/mutant/killer.rb index 3c445ea6..c424573d 100644 --- a/lib/mutant/killer.rb +++ b/lib/mutant/killer.rb @@ -1,5 +1,5 @@ module Mutant - # Abstract runner for mutant killers + # Abstract base class for mutant killers class Killer include Adamantium::Flat, AbstractClass @@ -45,6 +45,14 @@ module Mutant mutation.source end + # Return strategy + # + # @return [Strategy] + # + # @api private + # + attr_reader :strategy + # Return name of killer # # @return [String] @@ -94,9 +102,20 @@ module Mutant # # @api private # - def initialize(mutation) - @mutation = mutation - @mutation.insert + def initialize(strategy, mutation) + @strategy, @mutation = strategy, mutation + + run_with_benchmark + end + + # Run with taking the time + # + # @return [undefined] + # + # @api private + # + def run_with_benchmark + mutation.insert start_time = Time.now @killed = run end_time = Time.now diff --git a/lib/mutant/killer/forking.rb b/lib/mutant/killer/forking.rb index b73693af..7c598dde 100644 --- a/lib/mutant/killer/forking.rb +++ b/lib/mutant/killer/forking.rb @@ -1,38 +1,38 @@ module Mutant class Killer - class Forked < self - def initialize(killer, mutation) - @killer = killer - super(mutation) - end +# class Forked < self +# def initialize(killer, mutation) +# @killer = killer +# super(mutation) +# end - def type - @killer.type - end +# def type +# @killer.type +# end - def run - fork do - @killer.new(@mutation) - end +# def run +# fork do +# @killer.new(@mutation) +# end - status = Process.wait2.last - status.exitstatus.zero? - end - end +# status = Process.wait2.last +# status.exitstatus.zero? +# end +# end - class Forking < self - include Equalizer.new(:killer) +# class Forking < self +# include Equalizer.new(:killer) - attr_reader :killer +# attr_reader :killer - def initialize(killer) - @killer = killer - end +# def initialize(strategy) +# @killer = killer +# end - def run(mutation) - Forked.new(@killer, mutation) - end - end +# def run(mutation) +# Forked.new(@killer, mutation) +# end +# end end end diff --git a/lib/mutant/killer/rspec.rb b/lib/mutant/killer/rspec.rb index e92f27d7..eea11ec5 100644 --- a/lib/mutant/killer/rspec.rb +++ b/lib/mutant/killer/rspec.rb @@ -91,7 +91,7 @@ module Mutant # @api private # def filename_pattern - "test_app/spec/**/*_spec.rb" + strategy.filename_pattern(mutation) end end end diff --git a/lib/mutant/loader.rb b/lib/mutant/loader.rb index dc72bea5..c0c9c89e 100644 --- a/lib/mutant/loader.rb +++ b/lib/mutant/loader.rb @@ -38,6 +38,7 @@ module Mutant # @api private # def run + puts source eval(source, TOPLEVEL_BINDING) end diff --git a/lib/mutant/reporter/cli.rb b/lib/mutant/reporter/cli.rb index 8e245dda..cb8897ef 100644 --- a/lib/mutant/reporter/cli.rb +++ b/lib/mutant/reporter/cli.rb @@ -37,9 +37,9 @@ module Mutant # def config(config) puts 'Mutant configuration:' - puts "Matcher: #{config.matcher.inspect}" - puts "Filter: #{config.filter.inspect}" - puts "Killer: #{config.killer.inspect}" + puts "Matcher: #{config.matcher.inspect}" + puts "Filter: #{config.filter.inspect}" + puts "Strategy: #{config.strategy.inspect}" end # Reporter killer diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index c72cce2d..b1966204 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -101,7 +101,7 @@ module Mutant # @api private # def kill(mutation) - killer = config.killer.new(mutation) + killer = config.strategy.kill(mutation) reporter.killer(killer) if killer.fail? @errors << killer diff --git a/lib/mutant/strategy.rb b/lib/mutant/strategy.rb new file mode 100644 index 00000000..90b071da --- /dev/null +++ b/lib/mutant/strategy.rb @@ -0,0 +1,45 @@ +module Mutant + class Strategy + include AbstractClass + + # Kill mutation + # + # @param [Mutation] + # + # @return [Killer] + # + # @api private + # + def self.kill(mutation) + killer.new(self, mutation) + end + + def self.killer + self::KILLER + end + + class Rspec < self + KILLER = Killer::Rspec + + class DM2 < self + + def self.filename_pattern(mutation) + name = mutation.subject.context.scope.name + + append = mutation.subject.matcher.kind_of?(Matcher::Method::Singleton) ? '/class_methods' : '' + + path = Inflector.underscore(name) + + p "spec/unit/#{path}#{append}/*_spec.rb" + end + + end + + class Unit < self + def self.filename_pattern(mutation) + 'spec/unit/**/*.rb' + end + end + end + end +end diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index 36eee92f..3fff802d 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -1,7 +1,7 @@ module Mutant # Subject of a mutation class Subject - include Adamantium::Flat, Enumerable + include Adamantium::Flat, Enumerable, Equalizer.new(:context, :matcher, :node) # Return context # diff --git a/locator.rb b/locator.rb new file mode 100644 index 00000000..7d0c785f --- /dev/null +++ b/locator.rb @@ -0,0 +1,87 @@ + +require 'spec_helper' + +describe Mutant::Matcher::Method::Instance, '.each' do + subject { object.each(scope) { |item| yields << item } } + + let(:object) { described_class } + let(:yields) { [] } + + context 'when scope is a Class' do + let(:scope) do + ancestor = Class.new do + def ancestor_method + end + end + + Class.new(ancestor) do + def self.name; 'SomeRandomClass'; end + + def public_method; end + public :public_method + + def protected_method; end + protected :protected_method + + def private_method; end + private :private_method + end + end + + it 'should yield instance method matchers' do + expected = [ + Mutant::Matcher::Method::Instance.new(scope, :public_method ), + Mutant::Matcher::Method::Instance.new(scope, :protected_method), + Mutant::Matcher::Method::Instance.new(scope, :private_method ) + ].sort_by(&:method_name) + + expect { subject }.to change { yields.dup }.from([]).to(expected) + end + end +end + +require 'spec_helper' + +describe Mutant::Matcher::Method::Singleton, '.each' do + subject { object.each(scope) { |item| yields << item } } + + let(:each_arguments) { [scope] } + + let(:object) { described_class } + let(:yields) { [] } + + context 'when scope is a Class' do + let(:scope) do + ancestor = Class.new do + def self.ancestor_method + end + + def self.name; 'SomeRandomClass'; end + end + + Class.new(ancestor) do + def self.public_method; end + public_class_method :public_method + + class << self + def protected_method; end + + protected :protected_method + end + + def self.private_method; end + private_class_method :private_method + end + end + + it 'should yield instance method matchers' do + expected = [ + Mutant::Matcher::Method::Singleton.new(scope, :public_method ), + Mutant::Matcher::Method::Singleton.new(scope, :protected_method), + Mutant::Matcher::Method::Singleton.new(scope, :private_method ) + ].sort_by(&:method_name) + + expect { subject }.to change { yields.dup }.from([]).to(expected) + end + end +end diff --git a/spec/unit/mutant/cli/class_methods/new_spec.rb b/spec/unit/mutant/cli/class_methods/new_spec.rb index a99065bc..527f5201 100644 --- a/spec/unit/mutant/cli/class_methods/new_spec.rb +++ b/spec/unit/mutant/cli/class_methods/new_spec.rb @@ -14,6 +14,11 @@ shared_examples_for 'a cli parser' do end describe Mutant::CLI, '.new' do + + before do + pending + end + let(:object) { described_class } # Defaults @@ -40,7 +45,7 @@ describe Mutant::CLI, '.new' do end context 'with code filter and missing argument' do - let(:arguments) { %w(--code) } + let(:arguments) { %w(--rspec-unit --code) } let(:expected_message) { '"--code" is missing an argument' } diff --git a/spec/unit/mutant/killer/class_methods/run_spec.rb b/spec/unit/mutant/killer/class_methods/run_spec.rb index 694da522..e540cb86 100644 --- a/spec/unit/mutant/killer/class_methods/run_spec.rb +++ b/spec/unit/mutant/killer/class_methods/run_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' -describe Mutant::Killer,'.run' do - subject { class_under_test.run(mutation) } +describe Mutant::Killer,'.new' do + subject { class_under_test.new(strategy,mutation) } + + let(:strategy) { mock('Strategy') } let(:mutation) { mock('Mutation') } diff --git a/spec/unit/mutant/killer/fail_ques_spec.rb b/spec/unit/mutant/killer/fail_ques_spec.rb index 4336bba4..a4734378 100644 --- a/spec/unit/mutant/killer/fail_ques_spec.rb +++ b/spec/unit/mutant/killer/fail_ques_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe Mutant::Killer,'#fail?' do subject { object.fail? } - let(:object) { class_under_test.run(mutation) } - let(:root) { mock('Root') } - let(:mutation) { mock('Mutation') } + let(:object) { class_under_test.new(strategy, mutation) } + let(:strategy) { mock('Strategy') } + let(:mutation) { mock('Mutation') } before do mutation.stub(:insert) diff --git a/spec/unit/mutant/mutator/literal/symbol_spec.rb b/spec/unit/mutant/mutator/literal/symbol_spec.rb index 3869b41e..ca33d883 100644 --- a/spec/unit/mutant/mutator/literal/symbol_spec.rb +++ b/spec/unit/mutant/mutator/literal/symbol_spec.rb @@ -6,7 +6,7 @@ describe Mutant::Mutator::Literal, 'symbol' do let(:source) { ':foo' } let(:mutations) do - %w(nil) << ":#{random_string}" + %w(nil) << ":s#{random_string}" end before do