From feb12c0cae43c72d482052d1a56f0f873a4ccc9b Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:21:46 +0100 Subject: [PATCH 01/51] Remove unused code --- lib/mutant/helper.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/mutant/helper.rb b/lib/mutant/helper.rb index c67133dd..19ca2a6f 100644 --- a/lib/mutant/helper.rb +++ b/lib/mutant/helper.rb @@ -19,20 +19,5 @@ module Mutant Marshal.load(Marshal.dump(object)) end - # Extract option from options hash - # - # @param [Hash] options - # @param [Object] key - # - # @return [Object] value - # - # @api private - # - def self.extract_option(options, key) - options.fetch(key) do - raise ArgumentError,"Missing #{key.inspect} in options" - end - end - end end From 034e47e361d8ca68e7349bb34e73947a67049903 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:24:14 +0100 Subject: [PATCH 02/51] Move method expansion constants to constant file --- lib/mutant/constants.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/mutant/constants.rb b/lib/mutant/constants.rb index cc4e9b7f..f9a4d456 100644 --- a/lib/mutant/constants.rb +++ b/lib/mutant/constants.rb @@ -10,6 +10,12 @@ module Mutant undef unless until when while yield ).map(&:to_sym).to_set.freeze + METHOD_NAME_EXPANSIONS = { + /\?\z/ => '_predicate', + /=\z/ => '_writer', + /!\z/ => '_bang' + }.freeze + BINARY_METHOD_OPERATOR_EXPANSIONS = { :<=> => :spaceship_operator, :=== => :case_equality_operator, From d34d8565d2ca1f667485bd389277ac676bd248e7 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:25:49 +0100 Subject: [PATCH 03/51] Add method specific subject subclass --- lib/mutant.rb | 1 + lib/mutant/subject.rb | 30 ++++++------- lib/mutant/subject/method.rb | 82 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 lib/mutant/subject/method.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index b64121e7..5e9a0e24 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -84,6 +84,7 @@ require 'mutant/loader' require 'mutant/context' require 'mutant/context/scope' require 'mutant/subject' +require 'mutant/subject/method' require 'mutant/matcher' require 'mutant/matcher/chain' require 'mutant/matcher/object_space' diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index 95a5286b..71c5762d 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, Equalizer.new(:context, :matcher, :node) + include AbstractType, Adamantium::Flat, Enumerable, Equalizer.new(:context, :node) # Return context # @@ -11,14 +11,6 @@ module Mutant # attr_reader :context - # Return matcher - # - # @return [Matcher] - # - # @api private - # - attr_reader :matcher - # Return AST node # # @return [Rubinius::AST::Node] @@ -84,7 +76,7 @@ module Mutant # @api private # def identification - "#{matcher.identification}:#{source_path}:#{source_line}" + "#{context.identitfication}#{subtype}:#{source_path}:#{source_line}" end memoize :identification @@ -126,19 +118,27 @@ module Mutant # Initialize subject # - # @param [Matcher] matcher - # the context of mutations + # @param [Mutant::Context] context # # @param [Rubinius::AST::Node] node - # the node to be mutated + # the original node to be mutated # # @return [unkown] # # @api private # - def initialize(matcher, context, node) - @matcher, @context, @node = matcher, context, node + def initialize(context, node) + @context, @node = context, node end + # Return subtype identifier + # + # @return [String] + # + # @api private + # + abstract_method :subtype + private :subtype + end end diff --git a/lib/mutant/subject/method.rb b/lib/mutant/subject/method.rb new file mode 100644 index 00000000..f82abc46 --- /dev/null +++ b/lib/mutant/subject/method.rb @@ -0,0 +1,82 @@ +module Mutant + class Subject + # Abstract base class for method subjects + class Method < self + + # Test if method is public + # + # @return [true] + # if method is public + # + # @return [false] + # otherwise + # + # @api private + # + abstract_method :public? + + # Instance method subjects + class Instance < self + + # Test if method is public + # + # @return [true] + # if method is public + # + # @return [false] + # otherwise + # + # @api private + # + def public? + scope.public_method_defined?(method_name) + end + memoize :public? + + private + + # Return subtype identifier + # + # @return [String] + # + # @api private + # + def subtype + "#{context.name}##{node.name}" + end + + end + + # Singleton method subjects + class Singleton < self + + # Test if method is public + # + # @return [true] + # if method is public + # + # @return [false] + # otherwise + # + # @api private + # + def public? + scope.singleton_class.public_method_defined?(method_name) + end + memoize :public? + + private + + # Return subtype identifier + # + # @return [String] + # + # @api private + # + def subtype + "#{context.name}.#{node.name}" + end + end + end + end +end From 26ad183fa424c051c4c10dffae2f2b4f14fa9ee8 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:26:31 +0100 Subject: [PATCH 04/51] Fix yard docs --- lib/mutant/context.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mutant/context.rb b/lib/mutant/context.rb index 144b6a4c..5e51ea3f 100644 --- a/lib/mutant/context.rb +++ b/lib/mutant/context.rb @@ -5,7 +5,9 @@ module Mutant # Return root ast node # - # @return [Rubinis::AST::Script] + # @param [Rubnius::AST::Node] node + # + # @return [Rubinis::AST::Node] # # @api private # From 2fc836d97f1a1e616177149f7411701af9d528dd Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:27:03 +0100 Subject: [PATCH 05/51] Add context identification --- lib/mutant/context.rb | 8 ++++++++ lib/mutant/context/scope.rb | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/mutant/context.rb b/lib/mutant/context.rb index 5e51ea3f..37c44c9d 100644 --- a/lib/mutant/context.rb +++ b/lib/mutant/context.rb @@ -21,6 +21,14 @@ module Mutant # attr_reader :source_path + # Return identification + # + # @return [String] + # + # @api private + # + abstract_method :identification + private # Initialize context diff --git a/lib/mutant/context/scope.rb b/lib/mutant/context/scope.rb index 2394e980..cf164835 100644 --- a/lib/mutant/context/scope.rb +++ b/lib/mutant/context/scope.rb @@ -16,6 +16,16 @@ module Mutant end end + # Return identification + # + # @return [String] + # + # @ai private + # + def identification + scope.name + end + # Wrap node into ast node # # @param [Class, Module] scope From e0f5b999714c3ad6551b69c55f276ac13701f3c8 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:27:14 +0100 Subject: [PATCH 06/51] Remove dead context code --- lib/mutant/context/scope.rb | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/mutant/context/scope.rb b/lib/mutant/context/scope.rb index cf164835..a5af3690 100644 --- a/lib/mutant/context/scope.rb +++ b/lib/mutant/context/scope.rb @@ -98,26 +98,6 @@ module Mutant @scope = scope end - # Return scope AST class - # - # @return [Rubinius::AST::Node] - # - # @api private - # - def scope_class - self.class::SCOPE_CLASS - end - - # Return keyword - # - # @return [Rubinius::AST::Node] - # - # @api private - # - def keyword - self.class::KEYWORD - end - # Return new root ast # # @return [Rubinius::AST::Node] From af33fd036631ea86e2c006eb8ea3306f0d4fd56b Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:29:07 +0100 Subject: [PATCH 07/51] Add standalone method expansions --- lib/mutant.rb | 1 + lib/mutant/strategy/method_expansion.rb | 51 +++++++++++++++++++ .../class_methods/run_spec.rb | 49 ++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 lib/mutant/strategy/method_expansion.rb create mode 100644 spec/unit/mutant/strategy/method_expansion/class_methods/run_spec.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index 5e9a0e24..649fd75d 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -98,6 +98,7 @@ require 'mutant/killer/static' require 'mutant/killer/rspec' require 'mutant/killer/forking' require 'mutant/strategy' +require 'mutant/strategy/method_expansion' require 'mutant/strategy/rspec' require 'mutant/strategy/rspec/example_lookup' require 'mutant/runner' diff --git a/lib/mutant/strategy/method_expansion.rb b/lib/mutant/strategy/method_expansion.rb new file mode 100644 index 00000000..96844809 --- /dev/null +++ b/lib/mutant/strategy/method_expansion.rb @@ -0,0 +1,51 @@ +module Mutant + class Strategy + module MethodExpansion + + # Run method name expansion + # + # @param [Symbol] name + # + # @return [Symbol] + # + # @api private + # + def self.run(name) + name = map(name) || expand(name) + end + + # Return mapped name + # + # @param [Symbol] name + # + # @return [Symbol] + # if name was mapped + # + # @return [nil] + # otherwise + # + # @api private + # + def self.map(name) + OPERATOR_EXPANSIONS[name] + end + private_class_method :map + + # Return expanded name + # + # @param [Symbol] name + # + # @return [Symbol] + # + # @api private + # + def self.expand(name) + METHOD_NAME_EXPANSIONS.inject(name) do |name, (regexp, expansion)| + name.to_s.gsub(regexp, expansion) + end.to_sym + end + private_class_method :expand + + end + end +end diff --git a/spec/unit/mutant/strategy/method_expansion/class_methods/run_spec.rb b/spec/unit/mutant/strategy/method_expansion/class_methods/run_spec.rb new file mode 100644 index 00000000..2201b4d3 --- /dev/null +++ b/spec/unit/mutant/strategy/method_expansion/class_methods/run_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Mutant::Strategy::MethodExpansion, '.run' do + subject { object.run(name) } + + let(:object) { described_class } + + context 'unexpandable and unmapped name' do + let(:name) { :foo } + + it { should be(:foo) } + end + + context 'expanded name' do + + context 'predicate' do + let(:name) { :foo? } + + it { should be(:foo_predicate) } + end + + context 'writer' do + let(:name) { :foo= } + + it { should be(:foo_writer) } + end + + context 'bang' do + let(:name) { :foo! } + + it { should be(:foo_bang) } + end + + end + + context 'operator expansions' do + + Mutant::OPERATOR_EXPANSIONS.each do |name, expansion| + context "#{name}" do + let(:name) { name } + + it "should expand to #{expansion}" do + should be(expansion) + end + end + end + + end +end From b6a6eddfc541ea48a26690f8bfd1f8b38865bdb0 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:30:06 +0100 Subject: [PATCH 08/51] Construct correct subjects from method matchers --- lib/mutant/matcher/method.rb | 19 +++---------------- lib/mutant/matcher/method/instance.rb | 17 +---------------- lib/mutant/matcher/method/singleton.rb | 19 ++----------------- 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/lib/mutant/matcher/method.rb b/lib/mutant/matcher/method.rb index 90a1dd1e..582cf79d 100644 --- a/lib/mutant/matcher/method.rb +++ b/lib/mutant/matcher/method.rb @@ -35,9 +35,8 @@ module Mutant return self if skip? - subject.tap do |subject| - yield subject if subject - end + util = subject + yield util if util self end @@ -68,18 +67,6 @@ module Mutant method.name end - # Test if method is public - # - # @return [true] - # if method is public - # - # @return [false] - # otherwise - # - # @api private - # - abstract_method :public? - private # Initialize method filter @@ -180,7 +167,7 @@ module Mutant def subject node = matched_node return unless node - Subject.new(self, context, node) + self.cass::SUBJECT_CLASS.new(context, self) end memoize :subject diff --git a/lib/mutant/matcher/method/instance.rb b/lib/mutant/matcher/method/instance.rb index 0dd0d50d..18c20720 100644 --- a/lib/mutant/matcher/method/instance.rb +++ b/lib/mutant/matcher/method/instance.rb @@ -3,7 +3,7 @@ module Mutant class Method < self # Matcher for instance methods class Instance < self - + SUBJECT_CLASS = Subject::Method::Instance # Return identification # @@ -15,21 +15,6 @@ module Mutant "#{scope.name}##{method_name}" end - # Test if method is public - # - # @return [true] - # if method is public - # - # @return [false] - # otherwise - # - # @api private - # - def public? - scope.public_method_defined?(method_name) - end - memoize :public? - private # Check if node is matched diff --git a/lib/mutant/matcher/method/singleton.rb b/lib/mutant/matcher/method/singleton.rb index 1211f66f..3d10651e 100644 --- a/lib/mutant/matcher/method/singleton.rb +++ b/lib/mutant/matcher/method/singleton.rb @@ -3,6 +3,7 @@ module Mutant class Method # Matcher for singleton methods class Singleton < self + SUBJECT_CLASS = Subject::Method::Singleton # Return identification # @@ -15,22 +16,6 @@ module Mutant end memoize :identification - # Test if method is public - # - # @return [true] - # if method is public - # - # @return [false] - # otherwise - # - # @api private - # - def public? - scope.singleton_class.public_method_defined?(method_name) - end - memoize :public? - - private # Test for node match @@ -104,7 +89,7 @@ module Mutant when Rubinius::AST::ConstantAccess receiver_name?(receiver) else - $stderr.puts "Unable to find singleton method definition only match receiver on Rubinius::AST::Self or Rubinius::AST::ConstantAccess, got #{receiver.class}" + $stderr.puts "Unable to find singleton method definition can only match receiver on Rubinius::AST::Self or Rubinius::AST::ConstantAccess, got #{receiver.class}" false end end From e4201b3dfb6ad08725f95a49e2a639b3c206a6bd Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 22:32:03 +0100 Subject: [PATCH 09/51] Update subject specs --- spec/unit/mutant/subject/context_spec.rb | 9 ++++++--- spec/unit/mutant/subject/each_spec.rb | 19 +++++++++++-------- spec/unit/mutant/subject/node_spec.rb | 8 ++++++-- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/spec/unit/mutant/subject/context_spec.rb b/spec/unit/mutant/subject/context_spec.rb index 08328856..6076897f 100644 --- a/spec/unit/mutant/subject/context_spec.rb +++ b/spec/unit/mutant/subject/context_spec.rb @@ -3,9 +3,12 @@ require 'spec_helper' describe Mutant::Subject, '#context' do subject { object.context } - let(:object) { described_class.new(matcher, context, ast) } - let(:matcher) { mock('Matcher') } - let(:ast) { mock('AST') } + let(:class_under_test) do + Class.new(described_class) + end + + let(:object) { class_under_test.new(context, node) } + let(:node) { mock('Node') } let(:context) { mock('Context') } it { should be(context) } diff --git a/spec/unit/mutant/subject/each_spec.rb b/spec/unit/mutant/subject/each_spec.rb index 7da1806c..f24047d9 100644 --- a/spec/unit/mutant/subject/each_spec.rb +++ b/spec/unit/mutant/subject/each_spec.rb @@ -3,14 +3,17 @@ require 'spec_helper' describe Mutant::Subject, '#each' do subject { object.each { |item| yields << item } } - let(:object) { described_class.new(matcher, context, ast) } - let(:matcher) { mock('Matcher') } - let(:root) { mock('Root AST') } - let(:ast) { mock('AST') } - let(:context) { mock('Context', :root => root) } - let(:mutant) { mock('Mutant') } - let(:mutation) { mock('Mutation') } - let(:yields) { [] } + let(:class_under_test) do + Class.new(described_class) + end + + let(:object) { class_under_test.new(context, ast) } + let(:root) { mock('Root Node') } + let(:ast) { mock('Node') } + let(:context) { mock('Context', :root => root) } + let(:mutant) { mock('Mutant') } + let(:mutation) { mock('Mutation') } + let(:yields) { [] } before do Mutant::Mutator.stub(:each).with(ast).and_yield(mutant).and_return(Mutant::Mutator) diff --git a/spec/unit/mutant/subject/node_spec.rb b/spec/unit/mutant/subject/node_spec.rb index c1526755..13f1e639 100644 --- a/spec/unit/mutant/subject/node_spec.rb +++ b/spec/unit/mutant/subject/node_spec.rb @@ -2,8 +2,12 @@ require 'spec_helper' describe Mutant::Subject, '#node' do subject { object.node } - let(:object) { described_class.new(matcher, context, node) } - let(:matcher) { mock('Matcher') } + + let(:class_under_test) do + Class.new(described_class) + end + + let(:object) { class_under_test.new(context, node) } let(:node) { mock('Node') } let(:context) { mock('Context') } From c9d762dbe93c50159463d3d6c2963927fc83b531 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 13 Jan 2013 23:47:00 +0100 Subject: [PATCH 10/51] Add better rspec dm2 spec lookup --- lib/mutant.rb | 4 +- lib/mutant/killer/rspec.rb | 2 +- lib/mutant/strategy/rspec/dm2.rb | 20 ++ lib/mutant/strategy/rspec/dm2/lookup.rb | 79 ++++++ .../strategy/rspec/dm2/lookup/method.rb | 135 ++++++++++ lib/mutant/strategy/rspec/example_lookup.rb | 163 ------------ .../lookup/method/instance/spec_files_spec.rb | 52 ++++ .../method/singleton/spec_files_spec.rb | 42 ++++ .../rspec/example_lookup/spec_file_spec.rb | 236 ------------------ 9 files changed, 332 insertions(+), 401 deletions(-) create mode 100644 lib/mutant/strategy/rspec/dm2.rb create mode 100644 lib/mutant/strategy/rspec/dm2/lookup.rb create mode 100644 lib/mutant/strategy/rspec/dm2/lookup/method.rb delete mode 100644 lib/mutant/strategy/rspec/example_lookup.rb create mode 100644 spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb create mode 100644 spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb delete mode 100644 spec/unit/mutant/strategy/rspec/example_lookup/spec_file_spec.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index 649fd75d..4372ee99 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -100,7 +100,9 @@ require 'mutant/killer/forking' require 'mutant/strategy' require 'mutant/strategy/method_expansion' require 'mutant/strategy/rspec' -require 'mutant/strategy/rspec/example_lookup' +require 'mutant/strategy/rspec/dm2' +require 'mutant/strategy/rspec/dm2/lookup' +require 'mutant/strategy/rspec/dm2/lookup/method' require 'mutant/runner' require 'mutant/cli' require 'mutant/color' diff --git a/lib/mutant/killer/rspec.rb b/lib/mutant/killer/rspec.rb index 2c7ee826..76cc921c 100644 --- a/lib/mutant/killer/rspec.rb +++ b/lib/mutant/killer/rspec.rb @@ -42,7 +42,7 @@ module Mutant def command_line_arguments %W( --fail-fast - ) + strategy.spec_files(mutation) + ) + strategy.spec_files(mutation.subject) end end end diff --git a/lib/mutant/strategy/rspec/dm2.rb b/lib/mutant/strategy/rspec/dm2.rb new file mode 100644 index 00000000..e5fa5915 --- /dev/null +++ b/lib/mutant/strategy/rspec/dm2.rb @@ -0,0 +1,20 @@ +module Mutant + class Strategy + class Rspec + # DM2-style strategy + class DM2 < self + + # Return filename pattern + # + # @return [Enumerable] + # + # @api private + # + def spec_files(mutation) + ExampleLookup.run(mutation) + end + + end + end + end +end diff --git a/lib/mutant/strategy/rspec/dm2/lookup.rb b/lib/mutant/strategy/rspec/dm2/lookup.rb new file mode 100644 index 00000000..ba9d2866 --- /dev/null +++ b/lib/mutant/strategy/rspec/dm2/lookup.rb @@ -0,0 +1,79 @@ +module Mutant + class Strategy + class Rspec + class DM2 + + # Example lookup for the rspec dm2 + class Lookup + include AbstractType, Adamantium::Flat, Equalizer.new(:subject) + + # Return subject + # + # @return [Subject] + # + # @api private + # + attr_reader :subject + + # Return glob expression + # + # @return [String] + # + # @api private + # + abstract_method :spec_files + + # Initalize object + # + # @param [Mutation] mutation + # + # @api private + # + def initialize(subject) + @subject = subject + end + + # Perform example lookup + # + # @param [Subject] subject + # + # @return [Enumerable] + # + # @api private + # + def self.run(subject) + new(subject).spec_files + end + + REGISTRY = {} + + # Register subject hander + # + # @param [Class:Subject] + # + # @return [undefined] + # + # @api private + # + def self.handle(subject_class) + REGISTRY[subject_class]=self + end + private_class_method :handle + + # Build lookup object + # + # @param [Subjecŧ] subject + # + # @return [Lookup] + # + # @api private + # + def self.build(subject) + REGISTRY.fetch(subject.class).new(subject) + end + + end + end + end + end +end diff --git a/lib/mutant/strategy/rspec/dm2/lookup/method.rb b/lib/mutant/strategy/rspec/dm2/lookup/method.rb new file mode 100644 index 00000000..03400e1f --- /dev/null +++ b/lib/mutant/strategy/rspec/dm2/lookup/method.rb @@ -0,0 +1,135 @@ +module Mutant + class Strategy + class Rspec + class DM2 + class Lookup + class Method < self + + # Return spec files + # + # @return [Enumerable] + # + # @api private + # + def spec_files + Dir.glob(glob_expression) + end + memoize :spec_files + + private + + # Return base path + # + # @return [String] + # + # @api private + # + def base_path + "spec/unit/#{Inflector.underscore(subject.context_name)}" + end + + # Return method name + # + # @return [Symbol] + # + # @api private + # + def method_name + subject.method_name + end + + # Test if method is public + # + # @return [true] + # if method is public + # + # @return [false] + # + # @api private + # + def public? + subject.public? + end + + # Return expanded name + # + # @return [String] + # + # @api private + # + def expanded_name + MethodExpansion.run(subject.method_name) + end + + # Return glob expression + # + # @return [String] + # + # @api private + # + def glob_expression + public? ? public_glob_expression : private_glob_expression + end + + # Return public glob expression + # + # @return [String] + # + # @api private + # + def public_glob_expression + "#{base_path}/#{expanded_name}_spec.rb" + end + + # Return private glob expression + # + # @return [String] + # + # @api private + # + def private_glob_expression + "#{base_path}/*_spec.rb" + end + + class Instance < self + handle(Subject::Method::Instance) + + private + + # Return glob expression + # + # @return [String] + # + # @api private + # + def glob_expression + if method_name == :initialize and !public? + return "#{private_glob_expression} #{base_path}/class_methods/new_spec.rb" + end + + super + end + end + + class Singleton < self + handle(Subject::Method::Singleton) + + private + + # Return base path + # + # @return [String] + # + # @api private + # + def base_path + "#{super}/class_methods" + end + end + + end + end + end + end + end +end diff --git a/lib/mutant/strategy/rspec/example_lookup.rb b/lib/mutant/strategy/rspec/example_lookup.rb deleted file mode 100644 index ed759754..00000000 --- a/lib/mutant/strategy/rspec/example_lookup.rb +++ /dev/null @@ -1,163 +0,0 @@ -module Mutant - class Strategy - class Rspec - - # Example lookup for rspec - class ExampleLookup - include Adamantium::Flat, Equalizer.new(:mutation) - - # Perform example lookup - # - # @param [Mutation] mutation - # - # @return [Enumerable] - # - # @api private - # - def self.run(mutation) - new(mutation).spec_files - end - - # Return mutation - # - # @return [Mutation] - # - # @api private - # - attr_reader :mutation - - # Return spec files - # - # @return [Enumerable] - # - # @api private - # - def spec_files - expression = glob_expression - files = Dir[expression] - - if files.empty? - $stderr.puts("Spec file(s): #{expression.inspect} not found for #{mutation.identification}") - end - - files - end - memoize :spec_files - - private - - # Return method matcher - # - # @return [Matcher] - # - # @api private - # - def matcher - mutation.subject.matcher - end - - EXPANSIONS = { - /\?\z/ => '_predicate', - /=\z/ => '_writer', - /!\z/ => '_bang' - } - - # Return spec file - # - # @return [String] - # - # @api private - # - def spec_file - "#{mapped_name || expanded_name}_spec.rb" - end - memoize :spec_file - - # Return mapped name - # - # @return [Symbol] - # if name was mapped - # - # @return [nil] - # otherwise - # - # @api private - # - def mapped_name - OPERATOR_EXPANSIONS[method_name] - end - - # Return expanded name - # - # @return [Symbol] - # - # @api private - # - def expanded_name - EXPANSIONS.inject(method_name) do |name, (regexp, expansion)| - name.to_s.gsub(regexp, expansion) - end.to_sym - end - - # Return method name - # - # @return [Symbol] - # - # @api private - # - def method_name - matcher.method_name - end - - # Return glob expression - # - # @return [String] - # - # @api private - # - def glob_expression - base = base_path - - if mutation.subject.matcher.public? - "#{base}/#{spec_file}" - else - "#{base}/*_spec.rb" - end - end - - # Return instance of singleton path appendo - # - # @return [String] - # - # @api private - # - def scope_append - matcher.kind_of?(Matcher::Method::Singleton) ? '/class_methods' : '' - end - memoize :scope_append - - # Return base path - # - # @return [String] - # - # @api private - # - def base_path - "spec/unit/#{Inflector.underscore(mutation.subject.context.scope.name)}#{scope_append}" - end - memoize :base_path - - # Initalize object - # - # @param [Mutation] mutation - # - # @api private - # - def initialize(mutation) - @mutation = mutation - end - - end - end - end -end diff --git a/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb new file mode 100644 index 00000000..5f1bbde1 --- /dev/null +++ b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Mutant::Strategy::Rspec::DM2::Lookup::Method::Instance, '#spec_files' do + subject { object.spec_files } + + let(:object) { described_class.new(mutation_subject) } + let(:mutation_subject) { mock('Subject', :method_name => method_name, :public? => is_public, :context_name => context_name) } + let(:context_name) { 'Foo' } + let(:method_name) { :bar } + let(:files) { 'Files'.freeze } + + this_example_group = 'Mutant::Strategy::Rspec::DM2::Lookup::Method::Instance#spec_files' + + shared_examples_for this_example_group do + it_should_behave_like 'an idempotent method' + + before do + if is_public + Mutant::Strategy::MethodExpansion.should_receive(:run).with(method_name).and_return(:expanded_name) + end + Dir.should_receive(:glob).with(expected_glob_expression).and_return(files) + end + + it { should be(files) } + it { should be_frozen } + end + + context 'with public method' do + let(:is_public) { true } + let(:expected_glob_expression) { 'spec/unit/foo/expanded_name_spec.rb' } + + it_should_behave_like this_example_group + end + + context 'with nonpublic method' do + let(:is_public) { false } + + context 'non initialize' do + let(:expected_glob_expression) { 'spec/unit/foo/*_spec.rb' } + + it_should_behave_like this_example_group + end + + context 'initialize' do + let(:method_name) { :initialize } + let(:expected_glob_expression) { 'spec/unit/foo/*_spec.rb spec/unit/foo/class_methods/new_spec.rb' } + + it_should_behave_like this_example_group + end + + end +end diff --git a/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb new file mode 100644 index 00000000..573d85eb --- /dev/null +++ b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Mutant::Strategy::Rspec::DM2::Lookup::Method::Singleton, '#spec_files' do + + subject { object.spec_files } + + let(:object) { described_class.new(mutation_subject) } + let(:mutation_subject) { mock('Subject', :method_name => method_name, :public? => is_public, :context_name => context_name) } + let(:context_name) { 'Foo' } + let(:method_name) { :bar } + let(:files) { 'Files'.freeze } + + this_example_group = 'Mutant::Strategy::Rspec::DM2::Lookup::Method::Singleton#spec_files' + + shared_examples_for this_example_group do + it_should_behave_like 'an idempotent method' + + before do + if is_public + Mutant::Strategy::MethodExpansion.should_receive(:run).with(method_name).and_return(:expanded_name) + end + Dir.should_receive(:glob).with(expected_glob_expression).and_return(files) + end + + it { should be(files) } + it { should be_frozen } + end + + context 'with public method' do + let(:is_public) { true } + let(:expected_glob_expression) { 'spec/unit/foo/class_methods/expanded_name_spec.rb' } + + it_should_behave_like this_example_group + end + + context 'with nonpublic method' do + let(:is_public) { false } + let(:expected_glob_expression) { 'spec/unit/foo/class_methods/*_spec.rb' } + + it_should_behave_like this_example_group + end +end diff --git a/spec/unit/mutant/strategy/rspec/example_lookup/spec_file_spec.rb b/spec/unit/mutant/strategy/rspec/example_lookup/spec_file_spec.rb deleted file mode 100644 index 76637ba5..00000000 --- a/spec/unit/mutant/strategy/rspec/example_lookup/spec_file_spec.rb +++ /dev/null @@ -1,236 +0,0 @@ -require 'spec_helper' - -describe Mutant::Strategy::Rspec::ExampleLookup, '#spec_file' do - - let(:object) { described_class.new(mutation) } - let(:mutation) { mock('Mutation', :subject => mutation_subject) } - let(:mutation_subject) { mock('Subject', :matcher => matcher) } - let(:matcher) { mock('Matcher', :method_name => method_name) } - - subject { object.send(:spec_file) } - - shared_examples_for 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' do - it_should_behave_like 'an idempotent method' - - it { should eql(expected_spec_file) } - it { should be_frozen } - end - - context 'negation operator' do - let(:method_name) { :'!' } - let(:expected_spec_file) { 'negation_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with unary match method' do - let(:method_name) { :~@ } - let(:expected_spec_file) { 'unary_match_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with unary substraction method' do - let(:method_name) { :-@ } - let(:expected_spec_file) { 'unary_substraction_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with unary addition method' do - let(:method_name) { :+@ } - let(:expected_spec_file) { 'unary_addition_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with bitwise xor method' do - let(:method_name) { :^ } - let(:expected_spec_file) { 'bitwise_xor_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with bitwise or method' do - let(:method_name) { :| } - let(:expected_spec_file) { 'bitwise_or_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with bitwise and method' do - let(:method_name) { :& } - let(:expected_spec_file) { 'bitwise_and_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with spaceship method' do - let(:method_name) { :<=> } - let(:expected_spec_file) { 'spaceship_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with case equality operator method' do - let(:method_name) { :=== } - let(:expected_spec_file) { 'case_equality_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with modulo operator method' do - let(:method_name) { :% } - let(:expected_spec_file) { 'modulo_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with exponentation operator method' do - let(:method_name) { :** } - let(:expected_spec_file) { 'exponentation_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with substraction operator method' do - let(:method_name) { :- } - let(:expected_spec_file) { 'substraction_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with addition operator method' do - let(:method_name) { :+ } - let(:expected_spec_file) { 'addition_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with greater than or equal to operator method' do - let(:method_name) { :>= } - let(:expected_spec_file) { 'greater_than_or_equal_to_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with less than or equal to operator method' do - let(:method_name) { :<= } - let(:expected_spec_file) { 'less_than_or_equal_to_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with greater than operator method' do - let(:method_name) { :> } - let(:expected_spec_file) { 'greater_than_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with less than operator method' do - let(:method_name) { :< } - let(:expected_spec_file) { 'less_than_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with right shift operator method' do - let(:method_name) { :>> } - let(:expected_spec_file) { 'right_shift_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with left shift operator method' do - let(:method_name) { :<< } - let(:expected_spec_file) { 'left_shift_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with division operator method' do - let(:method_name) { :/ } - let(:expected_spec_file) { 'division_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with multiplication operator method' do - let(:method_name) { :* } - let(:expected_spec_file) { 'multiplication_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with nomatch operator method' do - let(:method_name) { :'!~' } - let(:expected_spec_file) { 'nomatch_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with match operator method' do - let(:method_name) { :=~ } - let(:expected_spec_file) { 'match_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with inequality operator method' do - let(:method_name) { :'!=' } - let(:expected_spec_file) { 'inequality_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with equality operator method' do - let(:method_name) { :== } - let(:expected_spec_file) { 'equality_operator_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with element reader method' do - let(:method_name) { :[] } - let(:expected_spec_file) { 'element_reader_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with element writer method' do - let(:method_name) { :[]= } - - let(:expected_spec_file) { 'element_writer_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with writer method' do - let(:method_name) { :foo= } - let(:expected_spec_file) { 'foo_writer_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with bang method' do - let(:method_name) { :foo! } - let(:expected_spec_file) { 'foo_bang_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with predicate method' do - let(:method_name) { :foo? } - let(:expected_spec_file) { 'foo_predicate_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end - - context 'with regular method' do - let(:method_name) { :foo } - let(:expected_spec_file) { 'foo_spec.rb' } - - it_should_behave_like 'Mutant::Strategy::Rspec::ExampleLookup#spec_file' - end -end From 4b47d666d778a23f727187799592bfbf89def0a8 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 14 Jan 2013 12:54:31 +0100 Subject: [PATCH 11/51] Build subjects from method matching correctly --- lib/mutant/matcher/method.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/mutant/matcher/method.rb b/lib/mutant/matcher/method.rb index 582cf79d..f3a92155 100644 --- a/lib/mutant/matcher/method.rb +++ b/lib/mutant/matcher/method.rb @@ -80,8 +80,6 @@ module Mutant # def initialize(scope, method) @scope, @method = scope, method - # FIXME: cache public private should not be needed, loader should not override visibility! (But does currently) :( - public? end # Test if method is skipped @@ -167,7 +165,7 @@ module Mutant def subject node = matched_node return unless node - self.cass::SUBJECT_CLASS.new(context, self) + self.class::SUBJECT_CLASS.new(context, node) end memoize :subject From a0933916ca76b0406a6d42ca03a5a52908ae6bc2 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 14 Jan 2013 12:54:53 +0100 Subject: [PATCH 12/51] Simplify method matching integration spec Should be converted to a unit spec soon --- .../mutant/method_matching_spec.rb | 62 ++++++++++++++++--- spec/shared/method_match_behavior.rb | 39 ------------ 2 files changed, 52 insertions(+), 49 deletions(-) delete mode 100644 spec/shared/method_match_behavior.rb diff --git a/spec/integration/mutant/method_matching_spec.rb b/spec/integration/mutant/method_matching_spec.rb index 77e07946..3e219a19 100644 --- a/spec/integration/mutant/method_matching_spec.rb +++ b/spec/integration/mutant/method_matching_spec.rb @@ -7,6 +7,48 @@ describe Mutant, 'method matching' do end end + this_example = 'Mutant method matching' + + shared_examples_for this_example do + subject { p Mutant::Matcher::Method.parse(pattern).to_a } + + let(:values) { defaults.merge(expectation) } + + let(:method_name) { values.fetch(:method_name) } + let(:method_line) { values.fetch(:method_line) } + let(:method_arity) { values.fetch(:method_arity) } + let(:scope) { values.fetch(:scope) } + let(:node_class) { values.fetch(:node_class) } + + let(:node) { mutation_subject.node } + let(:context) { mutation_subject.context } + let(:mutation_subject) { subject.first } + + it 'should return one subject' do + subject.size.should be(1) + end + + it 'should have correct method name' do + name(node).should eql(method_name) + end + + it 'should have correct line number' do + node.line.should eql(method_line) + end + + it 'should have correct arity' do + arguments(node).required.length.should eql(method_arity) + end + + it 'should have correct scope in context' do + context.send(:scope).should eql(scope) + end + + it 'should have the correct node class' do + node.should be_a(node_class) + end + end + before do #eval(body, TOPLEVEL_BINDING, __FILE__, 0) eval(body) @@ -47,7 +89,7 @@ describe Mutant, 'method matching' do { :method_line => 2 } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end context 'when method is defined multiple times' do @@ -68,7 +110,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end context 'on the same line' do @@ -87,7 +129,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end context 'on the same line with differend scope' do @@ -106,7 +148,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end context 'when nested' do @@ -131,7 +173,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end context 'in module' do @@ -153,7 +195,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end end end @@ -194,7 +236,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end context 'when defined on constant' do @@ -216,7 +258,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end context 'outside namespace' do @@ -235,7 +277,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end end @@ -262,7 +304,7 @@ describe Mutant, 'method matching' do } end - it_should_behave_like 'a method match' + it_should_behave_like this_example end end end diff --git a/spec/shared/method_match_behavior.rb b/spec/shared/method_match_behavior.rb deleted file mode 100644 index 7257e7ac..00000000 --- a/spec/shared/method_match_behavior.rb +++ /dev/null @@ -1,39 +0,0 @@ -shared_examples_for 'a method match' do - subject { Mutant::Matcher::Method.parse(pattern).to_a } - - let(:values) { defaults.merge(expectation) } - - let(:method_name) { values.fetch(:method_name) } - let(:method_line) { values.fetch(:method_line) } - let(:method_arity) { values.fetch(:method_arity) } - let(:scope) { values.fetch(:scope) } - let(:node_class) { values.fetch(:node_class) } - - let(:node) { mutation_subject.node } - let(:context) { mutation_subject.context } - let(:mutation_subject) { subject.first } - - it 'should return one subject' do - subject.size.should be(1) - end - - it 'should have correct method name' do - name(node).should eql(method_name) - end - - it 'should have correct line number' do - node.line.should eql(method_line) - end - - it 'should have correct arity' do - arguments(node).required.length.should eql(method_arity) - end - - it 'should have correct scope in context' do - context.send(:scope).should eql(scope) - end - - it 'should have the correct node class' do - node.should be_a(node_class) - end -end From 72aa121eae1d34e454b53f59811795ef11099cd0 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 14 Jan 2013 12:56:12 +0100 Subject: [PATCH 13/51] Construct identification of subject correctly --- lib/mutant/subject.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index 71c5762d..e32b56d9 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -76,7 +76,7 @@ module Mutant # @api private # def identification - "#{context.identitfication}#{subtype}:#{source_path}:#{source_line}" + "#{context.identification}#{subtype}:#{source_path}:#{source_line}" end memoize :identification From 151fd084399e5eeeb085867205cc1b9f4f1bf4cc Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 14 Jan 2013 13:36:26 +0100 Subject: [PATCH 14/51] Cleanup leaked changes from refactoring --- lib/mutant/strategy/rspec.rb | 16 +--------------- lib/mutant/subject.rb | 2 +- lib/mutant/subject/method.rb | 2 +- spec/integration/mutant/method_matching_spec.rb | 2 +- .../killer/rspec/class_methods/new_spec.rb | 7 ++++--- .../mutant/subject/class_methods/new_spec.rb | 13 ------------- 6 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 spec/unit/mutant/subject/class_methods/new_spec.rb diff --git a/lib/mutant/strategy/rspec.rb b/lib/mutant/strategy/rspec.rb index 4949381d..491155b9 100644 --- a/lib/mutant/strategy/rspec.rb +++ b/lib/mutant/strategy/rspec.rb @@ -6,20 +6,6 @@ module Mutant KILLER = Killer::Forking.new(Killer::Rspec) - # DM2-style strategy - class DM2 < self - - # Return filename pattern - # - # @return [Enumerable] - # - # @api private - # - def spec_files(mutation) - ExampleLookup.run(mutation) - end - end - # Run all unit specs per mutation class Unit < self @@ -30,7 +16,7 @@ module Mutant # @api private # def spec_files(mutation) - ['spec/unit'] + Dir['spec/unit/**/*_spec.rb'] end end diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index e32b56d9..7fbe2e5d 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -76,7 +76,7 @@ module Mutant # @api private # def identification - "#{context.identification}#{subtype}:#{source_path}:#{source_line}" + "#{subtype}:#{source_path}:#{source_line}" end memoize :identification diff --git a/lib/mutant/subject/method.rb b/lib/mutant/subject/method.rb index f82abc46..16d8d0a6 100644 --- a/lib/mutant/subject/method.rb +++ b/lib/mutant/subject/method.rb @@ -42,7 +42,7 @@ module Mutant # @api private # def subtype - "#{context.name}##{node.name}" + "#{context.identification}##{node.name}" end end diff --git a/spec/integration/mutant/method_matching_spec.rb b/spec/integration/mutant/method_matching_spec.rb index 3e219a19..d33be567 100644 --- a/spec/integration/mutant/method_matching_spec.rb +++ b/spec/integration/mutant/method_matching_spec.rb @@ -10,7 +10,7 @@ describe Mutant, 'method matching' do this_example = 'Mutant method matching' shared_examples_for this_example do - subject { p Mutant::Matcher::Method.parse(pattern).to_a } + subject { Mutant::Matcher::Method.parse(pattern).to_a } let(:values) { defaults.merge(expectation) } diff --git a/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb b/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb index 244365f7..3d7736a9 100644 --- a/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +++ b/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb @@ -4,9 +4,10 @@ describe Mutant::Killer::Rspec, '.new' do subject { object.new(strategy, mutation) } - let(:strategy) { mock('Strategy', :spec_files => ['foo'], :error_stream => $stderr, :output_stream => $stdout) } - let(:context) { mock('Context') } - let(:mutation) { mock('Mutation') } + let(:strategy) { mock('Strategy', :spec_files => ['foo'], :error_stream => $stderr, :output_stream => $stdout) } + let(:context) { mock('Context') } + let(:mutation) { mock('Mutation', :subject => mutation_subject) } + let(:mutation_subject) { mock('Mutation Subject') } let(:object) { described_class } diff --git a/spec/unit/mutant/subject/class_methods/new_spec.rb b/spec/unit/mutant/subject/class_methods/new_spec.rb deleted file mode 100644 index 5bf0ac6f..00000000 --- a/spec/unit/mutant/subject/class_methods/new_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe Mutant::Subject, '.new' do - subject { object.new(matcher, context, ast) } - - let(:object) { described_class } - - let(:matcher) { mock('Matcher') } - let(:context) { mock('Context') } - let(:ast) { mock('AST') } - - it { should be_frozen } -end From bbc91b945731a579d805142f7cc1b9bb4d3e0427 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 14 Jan 2013 14:13:39 +0100 Subject: [PATCH 15/51] Reenable some cli parsing specs --- lib/mutant/cli.rb | 2 +- .../unit/mutant/cli/class_methods/new_spec.rb | 42 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index 5016626b..5ee2124a 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -80,7 +80,7 @@ module Mutant # @api private # def strategy - @strategy || raise(Error, 'no strategy was set!') + @strategy || raise(Error, 'No strategy was set!') @strategy.new(self) end memoize :strategy diff --git a/spec/unit/mutant/cli/class_methods/new_spec.rb b/spec/unit/mutant/cli/class_methods/new_spec.rb index 527f5201..e55a4368 100644 --- a/spec/unit/mutant/cli/class_methods/new_spec.rb +++ b/spec/unit/mutant/cli/class_methods/new_spec.rb @@ -7,27 +7,31 @@ shared_examples_for 'an invalid cli run' do end shared_examples_for 'a cli parser' do - its(:filter) { should eql(expected_filter) } - its(:killer) { should eql(expected_killer) } - its(:reporter) { should eql(expected_reporter) } - its(:matcher) { should eql(expected_matcher) } + its(:filter) { should eql(expected_filter) } + its(:strategy) { should eql(expected_strategy.new(subject)) } + its(:reporter) { should eql(expected_reporter) } + its(:matcher) { should eql(expected_matcher) } end describe Mutant::CLI, '.new' do - before do - pending - end - let(:object) { described_class } # Defaults - let(:expected_filter) { Mutant::Mutation::Filter::ALL } - let(:expected_killer) { Mutant::Killer::Rspec::Forking } - let(:expected_reporter) { Mutant::Reporter::CLI.new($stderr) } + let(:expected_filter) { Mutant::Mutation::Filter::ALL } + let(:expected_strategy) { Mutant::Strategy::Rspec::Unit } + let(:expected_reporter) { Mutant::Reporter::CLI.new($stderr) } subject { object.new(arguments) } + context 'with unknown flag' do + let(:arguments) { %w(--invalid) } + + let(:expected_message) { 'Unknown option: "--invalid"' } + + it_should_behave_like 'an invalid cli run' + end + context 'with unknown option' do let(:arguments) { %w(--invalid Foo) } @@ -36,10 +40,16 @@ describe Mutant::CLI, '.new' do it_should_behave_like 'an invalid cli run' end + context 'with many strategy flags' do + let(:arguments) { %w(--rspec-unit --rspec-dm2) } + + let(:expected_strategy) { Mutant::Strategy::Rspec::DM2 } + end + context 'without arguments' do let(:arguments) { [] } - let(:expected_message) { 'No matchers given' } + let(:expected_message) { 'No strategy was set!' } it_should_behave_like 'an invalid cli run' end @@ -53,7 +63,7 @@ describe Mutant::CLI, '.new' do end context 'with explicit method matcher' do - let(:arguments) { %w(TestApp::Literal#float) } + let(:arguments) { %w(--rspec-unit TestApp::Literal#float) } let(:expected_matcher) { Mutant::Matcher::Method.parse('TestApp::Literal#float') } @@ -61,8 +71,8 @@ describe Mutant::CLI, '.new' do end - context 'with library name' do - let(:arguments) { %w(::TestApp) } + context 'with namespace matcher' do + let(:arguments) { %w(--rspec-unit ::TestApp) } let(:expected_matcher) { Mutant::Matcher::ObjectSpace.new(%r(\ATestApp(\z|::))) } @@ -70,7 +80,7 @@ describe Mutant::CLI, '.new' do end context 'with code filter' do - let(:arguments) { %w(--code faa --code bbb TestApp::Literal#float) } + let(:arguments) { %w(--rspec-unit --code faa --code bbb TestApp::Literal#float) } let(:filters) do [ From d41d7eddb0ff77f5c718bdc7e95130659ec5e1e1 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Tue, 15 Jan 2013 23:46:05 +0100 Subject: [PATCH 16/51] Refactor reporter infrastructure --- lib/mutant.rb | 4 + lib/mutant/killer.rb | 49 +++-- lib/mutant/killer/forked.rb | 58 ++++++ lib/mutant/killer/forking.rb | 54 ----- lib/mutant/killer/rspec.rb | 12 +- lib/mutant/mutation.rb | 70 +++---- lib/mutant/mutation/evil.rb | 35 ++++ lib/mutant/mutation/neutral.rb | 35 ++++ lib/mutant/reporter.rb | 117 ++++++----- lib/mutant/reporter/cli.rb | 180 +++++----------- lib/mutant/reporter/stats.rb | 195 ++++++++++-------- lib/mutant/runner.rb | 59 +++--- lib/mutant/strategy.rb | 32 +-- lib/mutant/strategy/static.rb | 18 ++ lib/mutant/subject.rb | 4 +- lib/mutant/subject/method.rb | 2 +- spec/support/ice_nine_config.rb | 9 + .../unit/mutant/cli/class_methods/new_spec.rb | 5 + spec/unit/mutant/killer/fail_ques_spec.rb | 39 ---- .../killer/rspec/class_methods/new_spec.rb | 4 +- .../mutant/killer/success_predicate_spec.rb | 28 +++ 21 files changed, 519 insertions(+), 490 deletions(-) create mode 100644 lib/mutant/killer/forked.rb create mode 100644 lib/mutant/mutation/evil.rb create mode 100644 lib/mutant/mutation/neutral.rb create mode 100644 lib/mutant/strategy/static.rb create mode 100644 spec/support/ice_nine_config.rb delete mode 100644 spec/unit/mutant/killer/fail_ques_spec.rb create mode 100644 spec/unit/mutant/killer/success_predicate_spec.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index 4372ee99..b13dbe72 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -38,6 +38,8 @@ require 'mutant/helper' require 'mutant/random' require 'mutant/mutator' require 'mutant/mutation' +require 'mutant/mutation/evil' +require 'mutant/mutation/neutral' require 'mutant/mutation/filter' require 'mutant/mutation/filter/code' require 'mutant/mutation/filter/whitelist' @@ -97,7 +99,9 @@ require 'mutant/killer' require 'mutant/killer/static' require 'mutant/killer/rspec' require 'mutant/killer/forking' +require 'mutant/killer/forked' require 'mutant/strategy' +require 'mutant/strategy/static' require 'mutant/strategy/method_expansion' require 'mutant/strategy/rspec' require 'mutant/strategy/rspec/dm2' diff --git a/lib/mutant/killer.rb b/lib/mutant/killer.rb index afee3314..165af69b 100644 --- a/lib/mutant/killer.rb +++ b/lib/mutant/killer.rb @@ -1,20 +1,35 @@ module Mutant # Abstract base class for mutant killers class Killer - include Adamantium::Flat, AbstractType + include Adamantium::Flat, AbstractType, Equalizer.new(:strategy, :mutation, :killed?) # Test for kill failure # # @return [true] - # returns true when mutant was killed + # when mutant was killed # # @return [false] - # returns false otherwise + # otherwise # # @api private # - def fail? - !@killed + def success? + mutation.success?(self) + end + memoize :success? + + # Test if mutant was killed + # + # @return [true] + # if mutant was killed + # + # @return [false] + # otherwise + # + # @api private + # + def killed? + @killed end # Return runtime of killer @@ -25,14 +40,14 @@ module Mutant # attr_reader :runtime - # Return original source + # Return configuration # - # @return [String] + # @return [Configuration] # # @api private # - def original_source - mutation.original_source + def configuration + strategy.configuration end # Return mutated source @@ -45,14 +60,6 @@ module Mutant mutation.source end - # Return strategy - # - # @return [Strategy] - # - # @api private - # - attr_reader :strategy - # Return name of killer # # @return [String] @@ -63,6 +70,14 @@ module Mutant self::TYPE end + # Return strategy + # + # @return [Strategy] + # + # @api private + # + attr_reader :strategy + # Return identification # # @return [String] diff --git a/lib/mutant/killer/forked.rb b/lib/mutant/killer/forked.rb new file mode 100644 index 00000000..3ba5fe49 --- /dev/null +++ b/lib/mutant/killer/forked.rb @@ -0,0 +1,58 @@ +module Mutant + class Killer + + # Killer that executes other killer in forked environment + class Forked < self + + # Initialize object + # + # @param [Killer] killer + # @param [Strategy] strategy + # @param [Mutation] mutation + # + # @api private + # + def initialize(killer, strategy, mutation) + @killer = killer + super(strategy, mutation) + end + + # Return killer type + # + # @return [String] + # + # @api private + # + def type + @killer.type + end + + private + + # Run killer + # + # @return [true] + # if mutant was killed + # + # @return [false] + # otherwise + # + # @api private + # + def run + fork do + begin + killer = @killer.new(strategy, mutation) + Kernel.exit(killer.fail? ? 1 : 0) + rescue + Kernel.exit(1) + end + end + + status = Process.wait2.last + status.exitstatus.zero? + end + + end + end +end diff --git a/lib/mutant/killer/forking.rb b/lib/mutant/killer/forking.rb index 4fc864cf..cb5c0d9c 100644 --- a/lib/mutant/killer/forking.rb +++ b/lib/mutant/killer/forking.rb @@ -1,59 +1,6 @@ module Mutant class Killer - # Killer that executes other killer in forked environment - class Forked < self - - # Initialize object - # - # @param [Killer] killer - # @param [Strategy] strategy - # @param [Mutation] mutation - # - # @api private - # - def initialize(killer, strategy, mutation) - @killer = killer - super(strategy, mutation) - end - - # Return killer type - # - # @return [String] - # - # @api private - # - def type - @killer.type - end - - private - - # Run killer - # - # @return [true] - # if mutant was killed - # - # @return [false] - # otherwise - # - # @api private - # - def run - fork do - begin - killer = @killer.new(strategy, mutation) - Kernel.exit(killer.fail? ? 1 : 0) - rescue - Kernel.exit(1) - end - end - - status = Process.wait2.last - status.exitstatus.zero? - end - end - # A killer that executes other killer in forked environemnts class Forking < self include Equalizer.new(:killer) @@ -93,6 +40,5 @@ module Mutant end end - end end diff --git a/lib/mutant/killer/rspec.rb b/lib/mutant/killer/rspec.rb index 76cc921c..c15160a9 100644 --- a/lib/mutant/killer/rspec.rb +++ b/lib/mutant/killer/rspec.rb @@ -2,21 +2,11 @@ module Mutant class Killer # Runner for rspec tests class Rspec < self + TYPE = 'rspec'.freeze private - # Initialize rspec runner - # - # @return [undefined] - # - # @api private - # - def initialize(*) - @error_stream, @output_stream = StringIO.new, StringIO.new - super - end - # Run rspec test # # @return [true] diff --git a/lib/mutant/mutation.rb b/lib/mutant/mutation.rb index a2a66518..a3fa6b49 100644 --- a/lib/mutant/mutation.rb +++ b/lib/mutant/mutation.rb @@ -1,7 +1,20 @@ module Mutant # Represent a mutated node with its subject class Mutation - include Adamantium::Flat, Equalizer.new(:sha1) + include AbstractType, Adamantium::Flat, Equalizer.new(:sha1) + + # Initialize mutation object + # + # @param [Subject] subject + # @param [Rubinius::Node::AST] node + # + # @return [undefined] + # + # @api private + # + def initialize(subject, node) + @subject, @node = subject, node + end # Return mutation subject # @@ -30,6 +43,20 @@ module Mutant end memoize :root + # Test if killer is successful + # + # @param [Killer] killer + # + # @return [true] + # if killer is successful + # + # @return [false] + # otherwise + # + # @api private + # + abstract_method :success? + # Insert mutated node # # @return [self] @@ -95,46 +122,5 @@ module Mutant subject.source end - private - - # Initialize mutation object - # - # @param [Subject] subject - # @param [Rubinius::Node::AST] node - # - # @return [undefined] - # - # @api private - # - def initialize(subject, node) - @subject, @node = subject, node - end - - # Noop mutation - class Noop < self - - # Initialize object - # - # @param [Subject] subject - # - # @return [undefined] - # - # @api private - # - def initialize(subject) - super(subject, subject.node) - end - - # Return identification - # - # @return [String] - # - # @api private - # - def identification - "noop:#{super}" - end - memoize :identification - end end end diff --git a/lib/mutant/mutation/evil.rb b/lib/mutant/mutation/evil.rb new file mode 100644 index 00000000..a41a16e2 --- /dev/null +++ b/lib/mutant/mutation/evil.rb @@ -0,0 +1,35 @@ +module Mutant + class Mutation + # Evul mutation + class Evil < self + + # Return identification + # + # @return [String] + # + # @api private + # + def identification + "evil:#{super}" + end + memoize :identification + + # Test if killer is successful + # + # @param [Killer] killer + # + # @return [true] + # if killer killed mutation + # + # @return [false] + # otherwise + # + # @api private + # + def success?(killer) + killer.killed? + end + + end + end +end diff --git a/lib/mutant/mutation/neutral.rb b/lib/mutant/mutation/neutral.rb new file mode 100644 index 00000000..7741e77a --- /dev/null +++ b/lib/mutant/mutation/neutral.rb @@ -0,0 +1,35 @@ +module Mutant + class Mutation + # Neutral mutation + class Neutral < self + + # Return identification + # + # @return [String] + # + # @api private + # + def identification + "noop:#{super}" + end + memoize :identification + + # Test if killer is successful + # + # @param [Killer] killer + # + # @return [true] + # if killer did NOT killed mutation + # + # @return [false] + # otherwise + # + # @api private + # + def success?(killer) + !killer.killed? + end + + end + end +end diff --git a/lib/mutant/reporter.rb b/lib/mutant/reporter.rb index 469efc11..373d9dcb 100644 --- a/lib/mutant/reporter.rb +++ b/lib/mutant/reporter.rb @@ -1,7 +1,18 @@ module Mutant # Abstract reporter class Reporter - include Adamantium::Flat, AbstractType + include Adamantium::Flat, AbstractType, Equalizer.new(:stats) + + # Initialize reporter + # + # @param [Config] config + # + # @api private + # + def initialize(config) + @stats = Stats.new + @config = config + end # Report subject # @@ -11,7 +22,10 @@ module Mutant # # @api private # - abstract_method :subject + def subject(subject) + stats.count_subject + self + end # Report mutation # @@ -21,17 +35,9 @@ module Mutant # # @api private # - abstract_method :mutation - - # Report notice - # - # @param [String] notice - # - # @return [self] - # - # @api private - # - abstract_method :notice + def mutation(mutation) + self + end # Report killer # @@ -41,44 +47,55 @@ module Mutant # # @api private # - abstract_method :killer + def report_killer(killer) + stats.count_killer(killer) - # Report config - # - # @param [Mutant::Config] config - # - # @return [self] - # - # @api private - # - abstract_method :config - - # Return output stream - # - # @return [IO] - # - # @api private - # - abstract_method :output_stream - - # Return error stream - # - # @return [IO] - # - # @api private - # - abstract_method :error_stream - - private - - # Initialize reporter - # - # @param [Config] config - # - # @api private - # - def initialize(config) - @config = config + self end + + # Test for running in debug mode + # + # @return [true] + # if running in debug mode + # + # @return [false] + # otherwise + # + # @api private + # + def debug? + config.debug? + end + + # Return stats + # + # @return [Reporter::Stats] + # + # @api private + # + attr_reader :stats + + # Return config + # + # @return [Config] + # + # @api private + # + attr_reader :config + + # Test if errors are present + # + # @return [true] + # if errors are present + # + # @return [false] + # otherwise + # + # @api private + # + def errors? + stats.errors? + end + end end diff --git a/lib/mutant/reporter/cli.rb b/lib/mutant/reporter/cli.rb index 0a7b3f8c..c5f7ded2 100644 --- a/lib/mutant/reporter/cli.rb +++ b/lib/mutant/reporter/cli.rb @@ -2,9 +2,21 @@ module Mutant class Reporter # Reporter that reports in human readable format class CLI < self - include Equalizer.new(:io) - # Reporter subject + # Initialize reporter + # + # @param [Config] config + # + # @return [undefined] + # + # @api private + # + def initialize(config) + super + @io = $stdout + end + + # Reporte subject # # @param [Subject] subject # @@ -13,7 +25,7 @@ module Mutant # @api private # def subject(subject) - stats.subject + super puts("Subject: #{subject.identification}") end @@ -24,7 +36,7 @@ module Mutant # @api private # def error_stream - @config.debug? ? io : StringIO.new + debug? ? io : StringIO.new end # Return output stream @@ -34,7 +46,7 @@ module Mutant # @api private # def output_stream - @config.debug? ? io : StringIO.new + debug? ? io : StringIO.new end # Report mutation @@ -46,7 +58,9 @@ module Mutant # @api private # def mutation(mutation) - if @config.debug? + super + + if debug? colorized_diff(mutation) end @@ -61,96 +75,37 @@ module Mutant # # @api private # - def config(config) - puts 'Mutant configuration:' - puts "Matcher: #{config.matcher.inspect}" - puts "Filter: #{config.filter.inspect}" - puts "Strategy: #{config.strategy.inspect}" + def print_config + message = [] + message << 'Mutant configuration:' + message << "Matcher: #{config.matcher.inspect}" + message << "Filter: #{config.filter.inspect}" + message << "Strategy: #{config.strategy.inspect}" + puts message.join("\n") end - # Report noop - # - # @param [Killer] killer - # - # @return [self] - # - # @api private - # - def noop(killer) - color, word = - if killer.fail? - [Color::GREEN, 'Alive'] - else - [Color::RED, 'Killed'] - end + ## Reporter killer + ## + ## @param [Killer] killer + ## + ## @return [self] + ## + ## @api private + ## + #def killer(killer) + # super - print_killer(color, word, killer) + # status = killer.killed? ? 'Killed' : 'Alive' + # color = killer.success? ? Color::GREEN : Color::RED - unless killer.fail? - puts(killer.mutation.source) - stats.noop_fail(killer) - end + # puts(colorize(color, "#{status}: #{killer.identification} (%02.2fs)" % killer.runtime)) - self - end + # unless killer.success? + # colorized_diff(killer.mutation) + # end - # Reporter killer - # - # @param [Killer] killer - # - # @return [self] - # - # @api private - # - def killer(killer) - stats.killer(killer) - - color, word = - if killer.fail? - [Color::RED, 'Alive'] - else - [Color::GREEN, 'Killed'] - end - - print_killer(color, word, killer) - - if killer.fail? - colorized_diff(killer.mutation) - end - - self - end - - # Report errors - # - # @param [Enumerable] errors - # - # @api private - # - # @return [self] - # - def errors(errors) - errors.each do |error| - failure(error) - end - - puts - puts "subjects: #{stats.subjects}" - puts "mutations: #{stats.mutations}" - puts "noop_fails: #{stats.noop_fails}" - puts "kills: #{stats.kills}" - puts "alive: #{stats.alive}" - puts "mtime: %02.2fs" % stats.time - puts "rtime: %02.2fs" % stats.runtime - end - - # Return IO stream - # - # @return [IO] - # - # @api private - # - attr_reader :io + # self + #end # Return stats # @@ -162,33 +117,13 @@ module Mutant private - # Initialize reporter + # Return IO stream # - # @param [Config] config - # - # @return [undefined] + # @return [IO] # # @api private - # - def initialize(config) - super - @io = $stdout - @stats = Stats.new - end - - # Report failure on killer # - # @param [Killer] killer - # - # @return [undefined] - # - # @api private - # - def failure(killer) - puts(colorize(Color::RED, "!!! Mutant alive: #{killer.identification} !!!")) - colorized_diff(killer.mutation) - puts("Took: (%02.2fs)" % killer.runtime) - end + attr_reader :io # Test for colored output # @@ -241,7 +176,7 @@ module Mutant # @api private # def colorized_diff(mutation) - if mutation.kind_of?(Mutation::Noop) + if mutation.kind_of?(Mutation::Neutral) puts mutation.original_source return end @@ -252,27 +187,13 @@ module Mutant # FIXME remove this branch before release if diff.empty? - raise "Unable to create a diff, so ast mutation or to_source has an error!" + raise 'Unable to create a diff, so ast mutation or to_source has an error!' end puts(diff) self end - # Print killer - # - # @param [Color] color - # @param [String] word - # @param [Killer] killer - # - # @return [undefined] - # - # @api private - # - def print_killer(color, word, killer) - puts(colorize(color, "#{word}: #{killer.identification} (%02.2fs)" % killer.runtime)) - end - # Test for output to tty # # @return [true] @@ -287,6 +208,7 @@ module Mutant @io.respond_to?(:tty?) && @io.tty? end memoize :tty? + end end end diff --git a/lib/mutant/reporter/stats.rb b/lib/mutant/reporter/stats.rb index cdb28d3d..094dddff 100644 --- a/lib/mutant/reporter/stats.rb +++ b/lib/mutant/reporter/stats.rb @@ -4,45 +4,48 @@ module Mutant # Stats gathered while reporter is running class Stats - # Return subject count - # - # @return [Fixnum] - # - # @api private - # - attr_reader :subjects + # A counter with fail counts + class Counter + include Equalizer.new(:count, :fails) - # Return mutation count - # - # @return [Fixnum] - # - # @api private - # - attr_reader :mutations + attr_reader :count - # Return skip count - # - # @return [Fixnum] - # - # @api private - # - attr_reader :noop_fails + # Return fail count + # + # @return [Fixnum] + # + # @api private + # + attr_reader :fails - # Return kill count - # - # @return [Fixnum] - # - # @api private - # - attr_reader :kills + # Initialize object + # + # @return [undefined] + # + # @api private + # + def initialize + @count = @fails = 0 + end - # Return mutation runtime - # - # @return [Float] - # - # @api private - # - attr_reader :time + # Count killer + # + # @param [Killer] killer + # + # @return [self] + # + # @api private + # + def handle(killer) + @count += 1 + unless killer.success? + @fails += 1 + end + self + end + end + + include Equalizer.new(:start, :counts, :killers) # Initialize object # @@ -51,8 +54,76 @@ module Mutant # @api private # def initialize - @start = Time.now - @noop_fails = @subjects = @mutations = @kills = @time = 0 + @start = start + @counts = Hash.new(0) + @killers = {} + end + + protected + + # Return counts + # + # @return [Hash] + # + # @api private + # + attr_reader :counts + + # Return start time + # + # @return [Time] + # + # @api private + # + attr_reader :start + + # Return killers + # + # @return [Hash] + # + # @api private + # + attr_reader :killers + + public + + # Count subject + # + # @return [self] + # + # @api private + # + def count_subject + @counts[:subject] += 1 + end + + # Count killer + # + # @param [Killer] killer + # + # @return [self] + # + # @api private + # + def count_killer(killer) + counter = @killers[killer.mutation.class] ||= Counter.new + counter.handle(killer) + self + end + + # Test for errors + # + # @return [true] + # if there are errors + # + # @return [false] + # otherwise + # + def errors? + @killers.values.inject(0) do |count, counter| + p counter, count + count += counter.fails + end.nonzero? end # Return runtime in seconds @@ -65,56 +136,6 @@ module Mutant Time.now - @start end - # Count subject - # - # @return [self] - # - # @api private - # - def subject - @subjects +=1 - self - end - - # Return number of mutants alive - # - # @return [Fixnum] - # - # @api private - # - def alive - @mutations - @kills - end - - # Count noop mutation fail - # - # @param [Killer] killer - # - # @return [self] - # - # @api private - # - def noop_fail(killer) - @noop_fails += 1 - @time += killer.runtime - self - end - - # Count killer - # - # @param [Killer] killer - # - # @return [self] - # - # @api private - # - def killer(killer) - @mutations +=1 - @kills +=1 unless killer.fail? - @time += killer.runtime - self - end - end end end diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index bf9061e8..d9895332 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -4,14 +4,6 @@ module Mutant include Adamantium::Flat extend MethodObject - # Return killers with errors - # - # @return [Enumerable] - # - # @api private - # - attr_reader :errors - # Test for failure # # @return [true] @@ -23,7 +15,7 @@ module Mutant # @api private # def fail? - !errors.empty? + reporter.errors? end # Return config @@ -45,12 +37,18 @@ module Mutant # @api private # def initialize(config) - @config, @errors = config, [] - - util_reporter = reporter - util_reporter.config(config) + @config = config run - util_reporter.errors(@errors) + end + + # Return strategy + # + # @return [Strategy] + # + # @api private + # + def strategy + config.strategy end # Return reporter @@ -70,10 +68,14 @@ module Mutant # @api private # def run + reporter.print_config + util = strategy + util.setup config.matcher.each do |subject| reporter.subject(subject) run_subject(subject) end + util.teardown end # Run mutation killers on subject @@ -85,7 +87,7 @@ module Mutant # @api private # def run_subject(subject) - return unless noop(subject) + return unless test_noop(subject) subject.each do |mutation| next unless config.filter.match?(mutation) reporter.mutation(mutation) @@ -93,26 +95,22 @@ module Mutant end end - # Test for noop mutation - # - # @param [Subject] subject + # Test noop mutation # # @return [true] - # if noop mutation is okay + # if noop mutation is alive # # @return [false] # otherwise # # @api private # - def noop(subject) - killer = killer(subject.noop) - reporter.noop(killer) - unless killer.fail? - @errors << killer + def test_noop(subject) + noop = subject.noop + unless kill(noop) + reporter.noop(noop) return false end - true end @@ -121,7 +119,7 @@ module Mutant # @param [Mutation] mutation # # @return [true] - # if killer was unsuccessful + # if killer was successful # # @return [false] # otherwise @@ -130,11 +128,8 @@ module Mutant # def kill(mutation) killer = killer(mutation) - reporter.killer(killer) - - if killer.fail? - @errors << killer - end + reporter.report_killer(killer) + killer.success? end # Return killer for mutation @@ -144,7 +139,7 @@ module Mutant # @api private # def killer(mutation) - config.strategy.kill(mutation) + strategy.kill(mutation) end end end diff --git a/lib/mutant/strategy.rb b/lib/mutant/strategy.rb index a05c071b..62b57b8f 100644 --- a/lib/mutant/strategy.rb +++ b/lib/mutant/strategy.rb @@ -24,24 +24,24 @@ module Mutant @config = config end - # Return output stream + # Perform setup # - # @return [IO] + # @return [self] # # @api private # - def output_stream - config.reporter.output_stream + def setup + self end - # Return error stream + # Perform teardown # - # @return [IO] + # @return [self] # # @api private # - def error_stream - config.reporter.error_stream + def teardown + self end # Kill mutation @@ -65,21 +65,5 @@ module Mutant def killer self.class::KILLER end - - # Static strategies - class Static < self - include Equalizer.new - - # Always fail to kill strategy - class Fail < self - KILLER = Killer::Static::Fail - end - - # Always succeed to kill strategy - class Success < self - KILLER = Killer::Static::Success - end - - end end end diff --git a/lib/mutant/strategy/static.rb b/lib/mutant/strategy/static.rb new file mode 100644 index 00000000..8c12a7fd --- /dev/null +++ b/lib/mutant/strategy/static.rb @@ -0,0 +1,18 @@ +module Mutant + class Strategy + # Static strategies + class Static < self + + # Always fail to kill strategy + class Fail < self + KILLER = Killer::Static::Fail + end + + # Always succeed to kill strategy + class Success < self + KILLER = Killer::Static::Success + end + + end + end +end diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index 7fbe2e5d..cb7f0a1f 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -32,7 +32,7 @@ module Mutant def each return to_enum unless block_given? Mutator.each(node) do |mutant| - yield Mutation.new(self, mutant) + yield Mutation::Evil.new(self, mutant) end self @@ -45,7 +45,7 @@ module Mutant # @api private # def noop - Mutation::Noop.new(self) + Mutation::Neutral.new(self, node) end memoize :noop diff --git a/lib/mutant/subject/method.rb b/lib/mutant/subject/method.rb index 16d8d0a6..8d3d106e 100644 --- a/lib/mutant/subject/method.rb +++ b/lib/mutant/subject/method.rb @@ -74,7 +74,7 @@ module Mutant # @api private # def subtype - "#{context.name}.#{node.name}" + "#{context.identification}.#{node.body.name}" end end end diff --git a/spec/support/ice_nine_config.rb b/spec/support/ice_nine_config.rb new file mode 100644 index 00000000..5e077e59 --- /dev/null +++ b/spec/support/ice_nine_config.rb @@ -0,0 +1,9 @@ +require 'ice_nine' + +module IceNine + class Freezer + class RSpec < NoFreeze + 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 e55a4368..01d00b88 100644 --- a/spec/unit/mutant/cli/class_methods/new_spec.rb +++ b/spec/unit/mutant/cli/class_methods/new_spec.rb @@ -16,6 +16,11 @@ end describe Mutant::CLI, '.new' do let(:object) { described_class } + let(:time) { Time.now } + + before do + Time.stub(:now => time) + end # Defaults let(:expected_filter) { Mutant::Mutation::Filter::ALL } diff --git a/spec/unit/mutant/killer/fail_ques_spec.rb b/spec/unit/mutant/killer/fail_ques_spec.rb deleted file mode 100644 index a4734378..00000000 --- a/spec/unit/mutant/killer/fail_ques_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' - -describe Mutant::Killer,'#fail?' do - subject { object.fail? } - - let(:object) { class_under_test.new(strategy, mutation) } - let(:strategy) { mock('Strategy') } - let(:mutation) { mock('Mutation') } - - before do - mutation.stub(:insert) - mutation.stub(:reset) - end - - let(:class_under_test) do - kill_state = self.kill_state - Class.new(described_class) do - define_method :run do - kill_state - end - end - end - - context 'when mutant was killed' do - let(:kill_state) { true } - - it_should_behave_like 'an idempotent method' - - it { should be(false) } - end - - context 'when mutant was NOT killed' do - let(:kill_state) { false } - - it_should_behave_like 'an idempotent method' - - it { should be(true) } - end -end diff --git a/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb b/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb index 3d7736a9..515b7dfa 100644 --- a/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +++ b/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb @@ -20,14 +20,14 @@ describe Mutant::Killer::Rspec, '.new' do context 'when run exits zero' do let(:exit_status) { 0 } - its(:fail?) { should be(true) } + its(:killed?) { should be(false) } it { should be_a(described_class) } end context 'when run exits nonzero' do let(:exit_status) { 1 } - its(:fail?) { should be(false) } + its(:killed?) { should be(true) } it { should be_a(described_class) } end end diff --git a/spec/unit/mutant/killer/success_predicate_spec.rb b/spec/unit/mutant/killer/success_predicate_spec.rb new file mode 100644 index 00000000..829d3400 --- /dev/null +++ b/spec/unit/mutant/killer/success_predicate_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Mutant::Killer, '#success?' do + subject { object.success? } + + let(:object) { class_under_test.new(strategy, mutation) } + let(:strategy) { mock('Strategy') } + let(:mutation) { mock('Mutation', :success? => kill_state) } + let(:kill_state) { mock('Kill State') } + + before do + kill_state.stub(:freeze => kill_state, :dup => kill_state) + end + + let(:class_under_test) do + Class.new(described_class) do + def run + end + end + end + + it_should_behave_like 'an idempotent method' + + it 'should use kill state to gather success' do + mutation.should_receive(:success?).with(object).and_return(kill_state) + should be(kill_state) + end +end From c360585f33c9c357feaefc5e04476b76821ea6c8 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Wed, 16 Jan 2013 00:57:12 +0100 Subject: [PATCH 17/51] Simplify noop mutator node listings It is much easier to remove or add from a %w() array literal --- lib/mutant/mutator/node/noop.rb | 75 ++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/lib/mutant/mutator/node/noop.rb b/lib/mutant/mutator/node/noop.rb index 0649a5b0..7e125887 100644 --- a/lib/mutant/mutator/node/noop.rb +++ b/lib/mutant/mutator/node/noop.rb @@ -8,40 +8,47 @@ module Mutant handle(Rubinius::AST::Self) # Currently unhandled node classes. Feel free to contribute your mutator! - handle(Rubinius::AST::ZSuper) - handle(Rubinius::AST::ElementAssignment) - handle(Rubinius::AST::AttributeAssignment) - handle(Rubinius::AST::Not) - handle(Rubinius::AST::And) - handle(Rubinius::AST::Or) - handle(Rubinius::AST::Defined) - handle(Rubinius::AST::Super) - handle(Rubinius::AST::Next) - handle(Rubinius::AST::Break) - handle(Rubinius::AST::Match3) - handle(Rubinius::AST::ZSuper) - handle(Rubinius::AST::MultipleAssignment) - handle(Rubinius::AST::ScopedConstant) - handle(Rubinius::AST::LocalVariableAccess) - handle(Rubinius::AST::InstanceVariableAccess) - handle(Rubinius::AST::GlobalVariableAccess) - handle(Rubinius::AST::ClassVariableAccess) - handle(Rubinius::AST::ToplevelConstant) - handle(Rubinius::AST::Ensure) - handle(Rubinius::AST::Rescue) - handle(Rubinius::AST::DynamicString) - handle(Rubinius::AST::DynamicSymbol) - handle(Rubinius::AST::DynamicRegex) - handle(Rubinius::AST::File) - handle(Rubinius::AST::OpAssignOr19) - handle(Rubinius::AST::BlockPass19) - handle(Rubinius::AST::OpAssign1) - handle(Rubinius::AST::OpAssign2) - handle(Rubinius::AST::SplatValue) - handle(Rubinius::AST::ConstantAccess) - handle(Rubinius::AST::Yield) - handle(Rubinius::AST::Begin) - handle(Rubinius::AST::Rescue) + # + # FIXME: This list is mixed with some 1.8 only nodes that should be extracted + # + %w( + ZSuper + ElementAssignment + AttributeAssignment + Not + And + Or + Defined + Super + Next + Break + Match3 + ZSuper + MultipleAssignment + ScopedConstant + LocalVariableAccess + InstanceVariableAccess + GlobalVariableAccess + ClassVariableAccess + ToplevelConstant + Ensure + Rescue + DynamicString + DynamicSymbol + DynamicRegex + File + OpAssignOr19 + BlockPass19 + OpAssign1 + OpAssign2 + SplatValue + ConstantAccess + Yield + Begin + Rescue + ).each do |name| + handle(Rubinius::AST.const_get(name)) + end private From 62cf07f5e6fd4f2451ba5e095bd4dc46b934221b Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Wed, 16 Jan 2013 18:18:07 +0100 Subject: [PATCH 18/51] Remove debug prints --- lib/mutant/reporter/stats.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/mutant/reporter/stats.rb b/lib/mutant/reporter/stats.rb index 094dddff..afc7f3c9 100644 --- a/lib/mutant/reporter/stats.rb +++ b/lib/mutant/reporter/stats.rb @@ -120,9 +120,8 @@ module Mutant # otherwise # def errors? - @killers.values.inject(0) do |count, counter| - p counter, count - count += counter.fails + @killers.values.inject(0) do |fails, counter| + fails += counter.fails end.nonzero? end From 3562d4f627e51845e1689e1618d7e85570b8ff49 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Fri, 18 Jan 2013 21:38:46 +0100 Subject: [PATCH 19/51] Load spec_helper.rb for rspec strategies --- lib/mutant/strategy/rspec.rb | 11 +++++++++++ spec/integration/mutant/rspec_killer_spec.rb | 6 +++--- test_app/spec/spec_helper.rb | 3 +++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/mutant/strategy/rspec.rb b/lib/mutant/strategy/rspec.rb index 491155b9..eb53b553 100644 --- a/lib/mutant/strategy/rspec.rb +++ b/lib/mutant/strategy/rspec.rb @@ -6,6 +6,17 @@ module Mutant KILLER = Killer::Forking.new(Killer::Rspec) + # Setup rspec strategy + # + # @return [self] + # + # @api private + # + def setup + require('./spec/spec_helper.rb') + self + end + # Run all unit specs per mutation class Unit < self diff --git a/spec/integration/mutant/rspec_killer_spec.rb b/spec/integration/mutant/rspec_killer_spec.rb index 8dbcf568..182930a3 100644 --- a/spec/integration/mutant/rspec_killer_spec.rb +++ b/spec/integration/mutant/rspec_killer_spec.rb @@ -11,14 +11,14 @@ describe Mutant,'rspec integration' do let(:strategy) { Mutant::Strategy::Rspec::DM2 } specify 'allows to kill mutations' do - Kernel.system("bundle exec mutant -I lib -r test_app --rspec-dm2 ::TestApp::Literal#string").should be(true) + Kernel.system('bundle exec mutant --rspec-dm2 ::TestApp::Literal#string').should be(true) end specify 'fails to kill mutations when they are not covered' do - Kernel.system("bundle exec mutant -I lib -r test_app --rspec-dm2 ::TestApp::Literal#uncovered_string").should be(false) + Kernel.system('bundle exec mutant --rspec-dm2 ::TestApp::Literal#uncovered_string').should be(false) end specify 'fails when some mutations when are not covered' do - Kernel.system("bundle exec mutant -I lib -r test_app --rspec-dm2 ::TestApp::Literal").should be(false) + Kernel.system('bundle exec mutant --rspec-dm2 ::TestApp::Literal').should be(false) end end diff --git a/test_app/spec/spec_helper.rb b/test_app/spec/spec_helper.rb index 870045ac..30af8493 100644 --- a/test_app/spec/spec_helper.rb +++ b/test_app/spec/spec_helper.rb @@ -1,6 +1,9 @@ # encoding: utf-8 require 'rspec' + +$: << File.join(File.dirname(__FILE__), 'lib') + require 'test_app' # require spec support files and shared behavior From 9ccd9be022f6c3ad7f7265af40e8ee3c0c2b968a Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 20 Jan 2013 20:16:29 +0100 Subject: [PATCH 20/51] Bump version to 0.3.0 --- mutant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mutant.gemspec b/mutant.gemspec index 9fdc0fd9..e0771bce 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |gem| gem.name = 'mutant' - gem.version = '0.2.16' + gem.version = '0.3.0' gem.authors = [ 'Markus Schirp' ] gem.email = [ 'mbj@seonic.net' ] gem.description = 'Mutation testing for ruby' From 22adfb9f60318102b30660f244ef91acdb2fdef0 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 20 Jan 2013 20:23:40 +0100 Subject: [PATCH 21/51] Remove -I/--include and -r/--require cli options --- lib/mutant/cli.rb | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index 5ee2124a..ff232a3f 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -100,10 +100,6 @@ module Mutant OPTIONS = { '--code' => [:add_filter, Mutation::Filter::Code ], - '-I' => [:add_load_path ], - '--include' => [:add_load_path ], - '-r' => [:require_library ], - '--require' => [:require_library ], '--debug' => [:set_debug ], '-d' => [:set_debug ], '--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ], @@ -246,30 +242,6 @@ module Mutant consume(2) end - # Add load path - # - # @api private - # - # @return [undefined] - # - def add_load_path - $LOAD_PATH << current_option_value - consume(2) - end - - # Enable rspec - # - # @api private - # - # @return [self] - # - # @api private - # - def enable_rspec - consume(1) - @rspec = true - end - # Set debug mode # # @api private @@ -293,16 +265,5 @@ module Mutant consume(1) @strategy = strategy end - - # Require library - # - # @api private - # - # @return [undefined] - # - def require_library - require(current_option_value) - consume(2) - end end end From 0e6d3e83ea5427f1b0f7dc8e49f92bc784156c04 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 00:00:29 +0100 Subject: [PATCH 22/51] Do not freeze fixnum constants --- lib/mutant/matcher/method/classifier.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/mutant/matcher/method/classifier.rb b/lib/mutant/matcher/method/classifier.rb index a3cc763a..c6e66b56 100644 --- a/lib/mutant/matcher/method/classifier.rb +++ b/lib/mutant/matcher/method/classifier.rb @@ -13,10 +13,9 @@ module Mutant SCOPE_FORMAT = /\A([^#.]+)(\.|#)(.+)\z/.freeze # Positions of captured regexp groups - # Freezing fixnums to avoid their singleton classes are patched. - SCOPE_NAME_POSITION = 1.freeze - SCOPE_SYMBOL_POSITION = 2.freeze - METHOD_NAME_POSITION = 3.freeze + SCOPE_NAME_POSITION = 1 + SCOPE_SYMBOL_POSITION = 2 + METHOD_NAME_POSITION = 3 private_class_method :new From 0c1b33be7fb8ee018a61ddb6f6add0157b31182a Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 00:08:55 +0100 Subject: [PATCH 23/51] Use attr_reader for object space matcher --- lib/mutant/matcher/object_space.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mutant/matcher/object_space.rb b/lib/mutant/matcher/object_space.rb index 27831990..93938626 100644 --- a/lib/mutant/matcher/object_space.rb +++ b/lib/mutant/matcher/object_space.rb @@ -51,7 +51,7 @@ module Mutant # # @api private # - def scope_name_pattern; @scope_name_pattern; end + attr_reader :scope_name_pattern private From e261bc8fbc9288d25b4428acb4830e78e5f39af0 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 00:09:08 +0100 Subject: [PATCH 24/51] Remove cli parser from object space matcher --- lib/mutant/matcher/object_space.rb | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/lib/mutant/matcher/object_space.rb b/lib/mutant/matcher/object_space.rb index 93938626..417bdcea 100644 --- a/lib/mutant/matcher/object_space.rb +++ b/lib/mutant/matcher/object_space.rb @@ -4,27 +4,6 @@ module Mutant class ObjectSpace < self include Equalizer.new(:scope_name_pattern) - PATTERN = %r(\A::(.+)\z) - - # Parse matcher - # - # @param [String] input - # - # @return [Matcher::ObjectSpace] - # returns object space matcher if successful - # - # @return [nil] - # returns nil otherwise - # - # @api private - # - def self.parse(input) - match = PATTERN.match(input) - return unless match - - new(%r(\A#{Regexp.escape(match[1])}(\z|::))) - end - # Enumerate subjects # # @return [Enumerator] From 133343e6536673a297b8a8c598a9b7818d62f1b5 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 00:09:32 +0100 Subject: [PATCH 25/51] Use attribute reader instead if ivar access --- lib/mutant/matcher/object_space.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mutant/matcher/object_space.rb b/lib/mutant/matcher/object_space.rb index 417bdcea..f91586db 100644 --- a/lib/mutant/matcher/object_space.rb +++ b/lib/mutant/matcher/object_space.rb @@ -84,7 +84,7 @@ module Mutant # @api private # def emit_scope(scope) - if [::Module, ::Class].include?(scope.class) and @scope_name_pattern =~ scope.name + if [::Module, ::Class].include?(scope.class) and scope_name_pattern =~ scope.name yield scope end end From b024508ae98d159d0ee93e46565f874320751986 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 08:32:29 +0100 Subject: [PATCH 26/51] Remove needless value mutation --- lib/mutant/reporter/stats.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mutant/reporter/stats.rb b/lib/mutant/reporter/stats.rb index afc7f3c9..d9d6c3ac 100644 --- a/lib/mutant/reporter/stats.rb +++ b/lib/mutant/reporter/stats.rb @@ -121,7 +121,7 @@ module Mutant # def errors? @killers.values.inject(0) do |fails, counter| - fails += counter.fails + fails + counter.fails end.nonzero? end From a3fc233d959fbe0cc1a4f750c3f9a13349ccef55 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 20:08:30 +0100 Subject: [PATCH 27/51] Reorganize classifiers * Classifiers are matcher subclass and delegate to lazy builded matcher interfaces * Solves chicken egg problem between target library load and classifier matcher instantiation --- lib/mutant.rb | 8 +- lib/mutant/cli.rb | 35 ++--- lib/mutant/cli/classifier.rb | 129 ++++++++++++++++++ .../classifier/method.rb} | 87 ++++-------- lib/mutant/cli/classifier/namespace.rb | 34 +++++ lib/mutant/matcher.rb | 42 +----- lib/mutant/matcher/method.rb | 12 -- .../matcher/{scope_methods.rb => methods.rb} | 4 +- .../matcher/{object_space.rb => namespace.rb} | 44 +++--- .../mutant/method_matching_spec.rb | 2 +- 10 files changed, 242 insertions(+), 155 deletions(-) create mode 100644 lib/mutant/cli/classifier.rb rename lib/mutant/{matcher/method/classifier.rb => cli/classifier/method.rb} (53%) create mode 100644 lib/mutant/cli/classifier/namespace.rb rename lib/mutant/matcher/{scope_methods.rb => methods.rb} (96%) rename lib/mutant/matcher/{object_space.rb => namespace.rb} (65%) diff --git a/lib/mutant.rb b/lib/mutant.rb index b13dbe72..8399cf94 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -89,12 +89,11 @@ require 'mutant/subject' require 'mutant/subject/method' require 'mutant/matcher' require 'mutant/matcher/chain' -require 'mutant/matcher/object_space' require 'mutant/matcher/method' require 'mutant/matcher/method/singleton' require 'mutant/matcher/method/instance' -require 'mutant/matcher/scope_methods' -require 'mutant/matcher/method/classifier' +require 'mutant/matcher/methods' +require 'mutant/matcher/namespace' require 'mutant/killer' require 'mutant/killer/static' require 'mutant/killer/rspec' @@ -109,6 +108,9 @@ require 'mutant/strategy/rspec/dm2/lookup' require 'mutant/strategy/rspec/dm2/lookup/method' require 'mutant/runner' require 'mutant/cli' +require 'mutant/cli/classifier' +require 'mutant/cli/classifier/namespace' +require 'mutant/cli/classifier/method' require 'mutant/color' require 'mutant/differ' require 'mutant/reporter' diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index ff232a3f..9b5dcdb2 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -9,6 +9,19 @@ module Mutant EXIT_FAILURE = 1 EXIT_SUCCESS = 0 + OPTIONS = { + '--code' => [:add_filter, Mutation::Filter::Code ], + '--debug' => [:set_debug ], + '-d' => [:set_debug ], + '--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ], + '--rspec-full' => [:set_strategy, Strategy::Rspec::Full ], + '--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ], + '--static-fail' => [:set_strategy, Strategy::Static::Fail ], + '--static-success' => [:set_strategy, Strategy::Static::Success ] + }.freeze + + OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze + # Run cli with arguments # # @param [Array] arguments @@ -98,19 +111,6 @@ module Mutant private - OPTIONS = { - '--code' => [:add_filter, Mutation::Filter::Code ], - '--debug' => [:set_debug ], - '-d' => [:set_debug ], - '--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ], - '--rspec-full' => [:set_strategy, Strategy::Rspec::Full ], - '--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ], - '--static-fail' => [:set_strategy, Strategy::Static::Fail ], - '--static-success' => [:set_strategy, Strategy::Static::Success ] - }.freeze - - OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze - # Initialize CLI # # @param [Array] arguments @@ -204,15 +204,8 @@ module Mutant # def dispatch_matcher argument = current_argument - matcher = Mutant::Matcher.from_string(argument) - - unless matcher - raise Error, "Invalid matcher syntax: #{argument.inspect}" - end - - @matchers << matcher - consume(1) + @matchers << Classifier.build(argument) end # Process option argument diff --git a/lib/mutant/cli/classifier.rb b/lib/mutant/cli/classifier.rb new file mode 100644 index 00000000..fbaa4db0 --- /dev/null +++ b/lib/mutant/cli/classifier.rb @@ -0,0 +1,129 @@ +module Mutant + class CLI + # A classifier for input strings + class Classifier < Matcher + include AbstractType, Adamantium::Flat, Equalizer.new(:identification) + extend DescendantsTracker + + SCOPE_NAME_PATTERN = /[A-Za-z][A-Za-z_0-9]*/.freeze + METHOD_NAME_PATTERN = /[_A-Za-z][A-Za-z0-9_]*[!?=]?/.freeze + SCOPE_PATTERN = /(?:::)?#{SCOPE_NAME_PATTERN}(?:::#{SCOPE_NAME_PATTERN})*/.freeze + + SINGLETON_PATTERN = %r(\A(#{SCOPE_PATTERN})\z).freeze + + # Return constant + # + # @param [String] location + # + # @return [Class|Module] + # + # @api private + # + def self.constant_lookup(location) + location.gsub(%r(\A::), '').split('::').inject(::Object) do |parent, name| + parent.const_get(name) + end + end + + # Return matchers for input + # + # @param [String] input + # + # @return [Classifier] + # if a classifier handles the input + # + # @return [nil] + # otherwise + # + # @api private + # + def self.build(input) + classifiers = descendants.map do |descendant| + descendant.run(input) + end.compact + + raise if classifiers.length > 1 + + classifiers.first + end + + # Run classifier + # + # @return [Classifier] + # if input is handled by classifier + # + # @return [nil] + # otherwise + # + # @api private + # + def self.run(input) + match = self::REGEXP.match(input) + return unless match + + new(match) + end + + # No protected_class_method in ruby :( + class << self; protected :run; end + + # Enumerate subjects + # + # @return [self] + # if block given + # + # @return [Enumerator] + # otherwise + # + # @api private + # + def each(&block) + return to_enum unless block_given? + matcher.each(&block) + self + end + + # Return identification + # + # @return [String] + # + # @api private + # + def identification + match.to_s + end + memoize :identification + + private + + # Initialize object + # + # @param [MatchData] match + # + # @return [undefined] + # + # @api private + # + def initialize(match) + @match = match + end + + # Return match + # + # @return [MatchData] + # + # @api private + # + attr_reader :match + + # Return matcher + # + # @return [Matcher] + # + # @api private + # + abstract_method :matcher + + end + end +end diff --git a/lib/mutant/matcher/method/classifier.rb b/lib/mutant/cli/classifier/method.rb similarity index 53% rename from lib/mutant/matcher/method/classifier.rb rename to lib/mutant/cli/classifier/method.rb index c6e66b56..819ade59 100644 --- a/lib/mutant/matcher/method/classifier.rb +++ b/lib/mutant/cli/classifier/method.rb @@ -1,41 +1,20 @@ module Mutant - class Matcher - class Method < self - # A classifier for input strings - class Classifier - include Adamantium::Flat + class CLI + class Classifier + # Explicit method classifier + class Method < self TABLE = { - '.' => Matcher::ScopeMethods::Singleton, - '#' => Matcher::ScopeMethods::Instance + '.' => Matcher::Methods::Singleton, + '#' => Matcher::Methods::Instance }.freeze - SCOPE_FORMAT = /\A([^#.]+)(\.|#)(.+)\z/.freeze + REGEXP = %r(\A(#{SCOPE_PATTERN})([.#])(#{METHOD_NAME_PATTERN}\z)).freeze # Positions of captured regexp groups - SCOPE_NAME_POSITION = 1 - SCOPE_SYMBOL_POSITION = 2 - METHOD_NAME_POSITION = 3 - - private_class_method :new - - # Run classifier - # - # @param [String] input - # - # @return [Matcher::Method] - # returns matcher when input is in - # - # @return [nil] - # returns nil otherwise - # - # @api private - # - def self.run(input) - match = SCOPE_FORMAT.match(input) - return unless match - new(match).matcher - end + SCOPE_NAME_POSITION = 1 + SCOPE_SYMBOL_POSITION = 2 + METHOD_NAME_POSITION = 3 # Return method matcher # @@ -48,6 +27,8 @@ module Mutant end memoize :matcher + private + # Return method # # @return [Method, UnboundMethod] @@ -61,39 +42,6 @@ module Mutant end memoize :method, :freezer => :noop - # Return match - # - # @return [Matche] - # - # @api private - # - attr_reader :match - - private - - # Initialize matcher - # - # @param [MatchData] match - # - # @api private - # - def initialize(match) - @match = match - end - - # Return scope - # - # @return [Class|Module] - # - # @api private - # - def scope - scope_name.gsub(%r(\A::), '').split('::').inject(::Object) do |parent, name| - parent.const_get(name) - end - end - memoize :scope - # Return scope name # # @return [String] @@ -104,6 +52,16 @@ module Mutant match[SCOPE_NAME_POSITION] end + # Return scope + # + # @return [Class, Method] + # + # @api private + # + def scope + Classifier.constant_lookup(scope_name) + end + # Return method name # # @return [String] @@ -134,6 +92,7 @@ module Mutant TABLE.fetch(scope_symbol).new(scope) end memoize :scope_matcher + end end end diff --git a/lib/mutant/cli/classifier/namespace.rb b/lib/mutant/cli/classifier/namespace.rb new file mode 100644 index 00000000..0e73eac2 --- /dev/null +++ b/lib/mutant/cli/classifier/namespace.rb @@ -0,0 +1,34 @@ +module Mutant + class CLI + class Classifier + + # Namespace classifier + class Namespace < self + + REGEXP = %r(\A(#{SCOPE_PATTERN})\*\z).freeze + + private + + # Return matcher + # + # @return [Matcher] + # + # @api private + # + def matcher + Matcher::Namespace.new(namespace) + end + + # Return namespace + # + # @return [Class, Module] + # + # @api private + # + def namespace + Classifier.const_lookup(match.to_s) + end + end + end + end +end diff --git a/lib/mutant/matcher.rb b/lib/mutant/matcher.rb index 271720e9..d6b654e1 100644 --- a/lib/mutant/matcher.rb +++ b/lib/mutant/matcher.rb @@ -1,5 +1,5 @@ module Mutant - # Abstract matcher to find ASTs to mutate + # Abstract matcher to find subjects to mutate class Matcher include Adamantium::Flat, Enumerable, AbstractType extend DescendantsTracker @@ -8,7 +8,11 @@ module Mutant # # @api private # - # @return [undefined] + # @return [self] + # if block given + # + # @return [Enumerabe] + # otherwise # abstract_method :each @@ -19,39 +23,5 @@ module Mutant # @api private # abstract_method :identification - - # Return matcher - # - # @param [String] input - # - # @return [nil] - # returns nil as default implementation - # - # @api private - # - def self.parse(input) - nil - end - - # Return match from string - # - # @param [String] input - # - # @return [Matcher] - # returns matcher input if successful - # - # @return [nil] - # returns nil otherwise - # - # @api private - # - def self.from_string(input) - descendants.each do |descendant| - matcher = descendant.parse(input) - return matcher if matcher - end - - nil - end end end diff --git a/lib/mutant/matcher/method.rb b/lib/mutant/matcher/method.rb index f3a92155..c3daa845 100644 --- a/lib/mutant/matcher/method.rb +++ b/lib/mutant/matcher/method.rb @@ -4,18 +4,6 @@ module Mutant class Method < self include Adamantium::Flat, Equalizer.new(:identification) - # Parse a method string into filter - # - # @param [String] input - # - # @return [Matcher::Method] - # - # @api private - # - def self.parse(input) - Classifier.run(input) - end - # Methods within rbx kernel directory are precompiled and their source # cannot be accessed via reading source location BLACKLIST = %r(\Akernel/).freeze diff --git a/lib/mutant/matcher/scope_methods.rb b/lib/mutant/matcher/methods.rb similarity index 96% rename from lib/mutant/matcher/scope_methods.rb rename to lib/mutant/matcher/methods.rb index 93e687f6..7d0c17c7 100644 --- a/lib/mutant/matcher/scope_methods.rb +++ b/lib/mutant/matcher/methods.rb @@ -1,7 +1,7 @@ module Mutant class Matcher - # Abstract base class for matcher that returns subjects extracted from scope methods - class ScopeMethods < self + # Abstract base class for matcher that returns method subjects extracted from scope + class Methods < self include AbstractType # Return scope diff --git a/lib/mutant/matcher/object_space.rb b/lib/mutant/matcher/namespace.rb similarity index 65% rename from lib/mutant/matcher/object_space.rb rename to lib/mutant/matcher/namespace.rb index f91586db..41134880 100644 --- a/lib/mutant/matcher/object_space.rb +++ b/lib/mutant/matcher/namespace.rb @@ -1,16 +1,16 @@ module Mutant class Matcher - # Matcher against object space - class ObjectSpace < self - include Equalizer.new(:scope_name_pattern) + # Matcher for specific namespace + class Namespace < self + include Equalizer.new(:pattern) # Enumerate subjects # - # @return [Enumerator] - # returns subject enumerator when no block given - # # @return [self] - # returns self otherwise + # if block given + # + # @return [Enumerator] + # otherwise # # @api private # @@ -24,29 +24,41 @@ module Mutant self end - # Return scope name pattern + # Return namespace # - # @return [Regexp] + # @return [Class::Module] # # @api private # - attr_reader :scope_name_pattern + attr_reader :namespace + + MATCHERS = [Matcher::Methods::Singleton, Matcher::Methods::Instance] private # Initialize object space matcher # - # @param [Regexp] scope_name_pattern - # @param [Enumerable<#each(scope)>] matchers + # @param [Class, Module] namespace # # @return [undefined] # # @api private # - def initialize(scope_name_pattern, matchers = [Matcher::ScopeMethods::Singleton, Matcher::ScopeMethods::Instance]) - @scope_name_pattern, @matchers = scope_name_pattern, @matchers = matchers #[Method::Singleton, Method::Instance] + def initialize(namespace) + @namespace = namespace end + # Return pattern + # + # @return [Regexp] + # + # @api private + # + def pattern + %r(\A#{Regexp.escape(namespace_name)}(?:::)?\z) + end + memoize :pattern + # Yield matchers for scope # # @param [::Class,::Module] scope @@ -56,7 +68,7 @@ module Mutant # @api private # def emit_scope_matches(scope, &block) - @matchers.each do |matcher| + MATCHERS.each do |matcher| matcher.new(scope).each(&block) end end @@ -84,7 +96,7 @@ module Mutant # @api private # def emit_scope(scope) - if [::Module, ::Class].include?(scope.class) and scope_name_pattern =~ scope.name + if [::Module, ::Class].include?(scope.class) and pattern =~ scope.name yield scope end end diff --git a/spec/integration/mutant/method_matching_spec.rb b/spec/integration/mutant/method_matching_spec.rb index d33be567..8e6eb03c 100644 --- a/spec/integration/mutant/method_matching_spec.rb +++ b/spec/integration/mutant/method_matching_spec.rb @@ -10,7 +10,7 @@ describe Mutant, 'method matching' do this_example = 'Mutant method matching' shared_examples_for this_example do - subject { Mutant::Matcher::Method.parse(pattern).to_a } + subject { Mutant::CLI::Classifier.build(pattern).to_a } let(:values) { defaults.merge(expectation) } From eeb82ee8c63132141ed86ad8119ba1f54863ee86 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 22:56:52 +0100 Subject: [PATCH 28/51] Cleanup matcher and classifier interactions * Some smells remain but it becomes cleaner now --- lib/mutant/cli/classifier/method.rb | 2 + lib/mutant/matcher.rb | 19 +++++++ lib/mutant/matcher/method/instance.rb | 5 +- lib/mutant/matcher/methods.rb | 50 +++++++++--------- lib/mutant/matcher/namespace.rb | 8 +-- .../unit/mutant/cli/class_methods/new_spec.rb | 17 +++--- .../classifier/class_methods/build_spec.rb | 44 ++++++++++++++++ .../matcher/class_methods/from_string_spec.rb | 49 ----------------- .../matcher/class_methods/parse_spec.rb | 12 ----- .../method/class_methods/parse_spec.rb | 21 -------- .../classifier/class_methods/run_spec.rb | 52 ------------------- .../mutant/matcher/namespace/each_spec.rb | 25 +++++++++ .../object_space/class_methods/parse_spec.rb | 24 --------- .../mutant/matcher/object_space/each_spec.rb | 31 ----------- 14 files changed, 129 insertions(+), 230 deletions(-) create mode 100644 spec/unit/mutant/cli/classifier/class_methods/build_spec.rb delete mode 100644 spec/unit/mutant/matcher/class_methods/from_string_spec.rb delete mode 100644 spec/unit/mutant/matcher/class_methods/parse_spec.rb delete mode 100644 spec/unit/mutant/matcher/method/class_methods/parse_spec.rb delete mode 100644 spec/unit/mutant/matcher/method/classifier/class_methods/run_spec.rb create mode 100644 spec/unit/mutant/matcher/namespace/each_spec.rb delete mode 100644 spec/unit/mutant/matcher/object_space/class_methods/parse_spec.rb delete mode 100644 spec/unit/mutant/matcher/object_space/each_spec.rb diff --git a/lib/mutant/cli/classifier/method.rb b/lib/mutant/cli/classifier/method.rb index 819ade59..fbae944b 100644 --- a/lib/mutant/cli/classifier/method.rb +++ b/lib/mutant/cli/classifier/method.rb @@ -23,6 +23,7 @@ module Mutant # @api private # def matcher + # TODO: Honor law of demeter scope_matcher.matcher.new(scope, method) end memoize :matcher @@ -36,6 +37,7 @@ module Mutant # @api private # def method + # TODO: Honor law of demeter scope_matcher.methods.detect do |method| method.name == method_name end || raise("Cannot find #{method_name} for #{scope}") diff --git a/lib/mutant/matcher.rb b/lib/mutant/matcher.rb index d6b654e1..28a78c24 100644 --- a/lib/mutant/matcher.rb +++ b/lib/mutant/matcher.rb @@ -4,6 +4,25 @@ module Mutant include Adamantium::Flat, Enumerable, AbstractType extend DescendantsTracker + # Enumerate subjects + # + # @param [Object] input + # + # @return [self] + # if block given + # + # @return [Enumerator] + # + # @api private + # + def self.each(input, &block) + return to_enum(__method__, input) unless block_given? + + new(input).each(&block) + + self + end + # Enumerate subjects # # @api private diff --git a/lib/mutant/matcher/method/instance.rb b/lib/mutant/matcher/method/instance.rb index 18c20720..36e4b3b5 100644 --- a/lib/mutant/matcher/method/instance.rb +++ b/lib/mutant/matcher/method/instance.rb @@ -1,6 +1,6 @@ module Mutant class Matcher - class Method < self + class Method # Matcher for instance methods class Instance < self SUBJECT_CLASS = Subject::Method::Instance @@ -14,6 +14,7 @@ module Mutant def identification "#{scope.name}##{method_name}" end + memoize :identification private @@ -30,7 +31,7 @@ module Mutant # @api private # def match?(node) - node.line == source_line && + node.line == source_line && node.class == Rubinius::AST::Define && node.name == method_name end diff --git a/lib/mutant/matcher/methods.rb b/lib/mutant/matcher/methods.rb index 7d0c17c7..a1353ace 100644 --- a/lib/mutant/matcher/methods.rb +++ b/lib/mutant/matcher/methods.rb @@ -2,16 +2,28 @@ module Mutant class Matcher # Abstract base class for matcher that returns method subjects extracted from scope class Methods < self - include AbstractType + include AbstractType, Equalizer.new(:scope) # Return scope # - # @return [Class,Model] + # @return [Class, Model] # # @api private # attr_reader :scope + # Initialize object + # + # @param [Class,Module] scope + # + # @return [undefined] + # + # @api private + # + def initialize(scope) + @scope = scope + end + # Enumerate subjects # # @return [self] @@ -32,6 +44,16 @@ module Mutant self end + # Return method matcher class + # + # @return [Class:Matcher::Method] + # + # @api private + # + def matcher + self.class::MATCHER + end + # Return methods # # @return [Enumerable] @@ -45,15 +67,7 @@ module Mutant end memoize :methods - # Return method matcher class - # - # @return [Class:Matcher::Method] - # - # @api private - # - def matcher - self.class::MATCHER - end + private # Return method names # @@ -69,20 +83,6 @@ module Mutant object.protected_instance_methods(false) end - private - - # Initialize object - # - # @param [Class,Module] scope - # - # @return [undefined] - # - # @api private - # - def initialize(scope) - @scope = scope - end - # Emit matches for method # # @param [UnboundMethod, Method] method diff --git a/lib/mutant/matcher/namespace.rb b/lib/mutant/matcher/namespace.rb index 41134880..5696406c 100644 --- a/lib/mutant/matcher/namespace.rb +++ b/lib/mutant/matcher/namespace.rb @@ -55,7 +55,7 @@ module Mutant # @api private # def pattern - %r(\A#{Regexp.escape(namespace_name)}(?:::)?\z) + %r(\A#{Regexp.escape(namespace.name)}(?:::)?\z) end memoize :pattern @@ -69,7 +69,7 @@ module Mutant # def emit_scope_matches(scope, &block) MATCHERS.each do |matcher| - matcher.new(scope).each(&block) + matcher.each(scope, &block) end end @@ -82,7 +82,7 @@ module Mutant def scopes(&block) return to_enum(__method__) unless block_given? - ::ObjectSpace.each_object(Module) do |scope| + ::ObjectSpace.each_object(Module).each do |scope| emit_scope(scope, &block) end end @@ -96,7 +96,7 @@ module Mutant # @api private # def emit_scope(scope) - if [::Module, ::Class].include?(scope.class) and pattern =~ scope.name + if pattern =~ scope.name yield scope 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 01d00b88..79d1fc4e 100644 --- a/spec/unit/mutant/cli/class_methods/new_spec.rb +++ b/spec/unit/mutant/cli/class_methods/new_spec.rb @@ -60,26 +60,23 @@ describe Mutant::CLI, '.new' do end context 'with code filter and missing argument' do - let(:arguments) { %w(--rspec-unit --code) } - + let(:arguments) { %w(--rspec-unit --code) } let(:expected_message) { '"--code" is missing an argument' } it_should_behave_like 'an invalid cli run' end context 'with explicit method matcher' do - let(:arguments) { %w(--rspec-unit TestApp::Literal#float) } - - let(:expected_matcher) { Mutant::Matcher::Method.parse('TestApp::Literal#float') } + let(:arguments) { %w(--rspec-unit TestApp::Literal#float) } + let(:expected_matcher) { Mutant::CLI::Classifier::Method.new('TestApp::Literal#float') } it_should_behave_like 'a cli parser' end context 'with namespace matcher' do - let(:arguments) { %w(--rspec-unit ::TestApp) } - - let(:expected_matcher) { Mutant::Matcher::ObjectSpace.new(%r(\ATestApp(\z|::))) } + let(:arguments) { %w(--rspec-unit ::TestApp*) } + let(:expected_matcher) { Mutant::CLI::Classifier::Namespace.new('::TestApp*') } it_should_behave_like 'a cli parser' end @@ -94,8 +91,8 @@ describe Mutant::CLI, '.new' do ] end - let(:expected_matcher) { Mutant::Matcher::Method.parse('TestApp::Literal#float') } - let(:expected_filter) { Mutant::Mutation::Filter::Whitelist.new(filters) } + let(:expected_matcher) { Mutant::CLI::Classifier::Method.new('TestApp::Literal#float') } + let(:expected_filter) { Mutant::Mutation::Filter::Whitelist.new(filters) } it_should_behave_like 'a cli parser' end diff --git a/spec/unit/mutant/cli/classifier/class_methods/build_spec.rb b/spec/unit/mutant/cli/classifier/class_methods/build_spec.rb new file mode 100644 index 00000000..adbd5123 --- /dev/null +++ b/spec/unit/mutant/cli/classifier/class_methods/build_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe Mutant::CLI::Classifier, '.build' do + subject { described_class.build(input) } + + this_spec = 'Mutant::CLI::Classifier.build' + + shared_examples_for this_spec do + it 'shoud return expected instance' do + should eql(expected_class.new(expected_class::REGEXP.match(input))) + end + end + + context 'with explicit toplevel scope' do + + let(:input) { '::TestApp::Literal#string' } + let(:expected_class) { Mutant::CLI::Classifier::Method } + + it_should_behave_like this_spec + end + + context 'with instance method notation' do + + let(:input) { 'TestApp::Literal#string' } + let(:expected_class) { Mutant::CLI::Classifier::Method } + + it_should_behave_like this_spec + end + + context 'with singleton method notation' do + let(:input) { 'TestApp::Literal.string' } + let(:expected_class) { Mutant::CLI::Classifier::Method } + + it_should_behave_like this_spec + end + + context 'with invalid notation' do + let(:input) { '::' } + + it 'should return nil' do + should be(nil) + end + end +end diff --git a/spec/unit/mutant/matcher/class_methods/from_string_spec.rb b/spec/unit/mutant/matcher/class_methods/from_string_spec.rb deleted file mode 100644 index b20f366b..00000000 --- a/spec/unit/mutant/matcher/class_methods/from_string_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'spec_helper' - -describe Mutant::Matcher, '.from_string' do - subject { object.from_string(input) } - - let(:input) { mock('Input') } - let(:matcher) { mock('Matcher') } - - let(:descendant_a) { mock('Descendant A', :parse => nil) } - let(:descendant_b) { mock('Descendant B', :parse => nil) } - - let(:object) { described_class } - - before do - described_class.stub(:descendants => [descendant_a, descendant_b]) - end - - context 'when no descendant takes the input' do - it { should be(nil) } - - it_should_behave_like 'an idempotent method' - end - - context 'when one descendant handles input' do - before do - descendant_a.stub(:parse => matcher) - end - - it { should be(matcher) } - - it_should_behave_like 'an idempotent method' - end - - context 'when more than one descendant handles input' do - let(:matcher_b) { mock('Matcher B') } - - before do - descendant_a.stub(:parse => matcher) - descendant_b.stub(:parse => matcher_b) - end - - it 'should return the first matcher' do - should be(matcher) - end - - it_should_behave_like 'an idempotent method' - end -end - diff --git a/spec/unit/mutant/matcher/class_methods/parse_spec.rb b/spec/unit/mutant/matcher/class_methods/parse_spec.rb deleted file mode 100644 index 1f2971d7..00000000 --- a/spec/unit/mutant/matcher/class_methods/parse_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'spec_helper' - -describe Mutant::Matcher, '.parse' do - subject { object.parse(input) } - - let(:input) { mock('Input') } - let(:object) { described_class } - - it { should be(nil) } - - it_should_behave_like 'an idempotent method' -end diff --git a/spec/unit/mutant/matcher/method/class_methods/parse_spec.rb b/spec/unit/mutant/matcher/method/class_methods/parse_spec.rb deleted file mode 100644 index 4b2f8dd3..00000000 --- a/spec/unit/mutant/matcher/method/class_methods/parse_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'spec_helper' - -describe Mutant::Matcher::Method, '.parse' do - subject { described_class.parse(input) } - - let(:response) { mock('Response') } - let(:input) { mock('Input') } - - let(:classifier) { described_class::Classifier } - - before do - classifier.stub(:run => response) - end - - it { should be(response) } - - it 'should call classifier' do - classifier.should_receive(:run).with(input).and_return(response) - subject - end -end diff --git a/spec/unit/mutant/matcher/method/classifier/class_methods/run_spec.rb b/spec/unit/mutant/matcher/method/classifier/class_methods/run_spec.rb deleted file mode 100644 index a6aef20d..00000000 --- a/spec/unit/mutant/matcher/method/classifier/class_methods/run_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'spec_helper' - -describe Mutant::Matcher::Method::Classifier, '.run' do - subject { described_class.run(input) } - - shared_examples_for 'Mutant::Matcher::Method::Classifier.run' do - before do - expected_class.stub(:new => response) - end - - let(:response) { :Response } - - it { should be(response) } - - it 'should initialize method filter with correct arguments' do - expected_class.should_receive(:new).with(TestApp::Literal, expected_method).and_return(response) - subject - end - end - - context 'with explicit toplevel scope' do - let(:input) { '::TestApp::Literal#string' } - let(:expected_class) { Mutant::Matcher::Method::Instance } - let(:expected_method) { TestApp::Literal.instance_method(:string) } - - it_should_behave_like 'Mutant::Matcher::Method::Classifier.run' - end - - context 'with instance method notation' do - let(:input) { 'TestApp::Literal#string' } - let(:expected_method) { TestApp::Literal.instance_method(:string) } - let(:expected_class) { Mutant::Matcher::Method::Instance } - - it_should_behave_like 'Mutant::Matcher::Method::Classifier.run' - end - - context 'with singleton method notation' do - let(:input) { 'TestApp::Literal.string' } - let(:expected_method) { TestApp::Literal.method(:string) } - let(:expected_class) { Mutant::Matcher::Method::Singleton } - - it_should_behave_like 'Mutant::Matcher::Method::Classifier.run' - end - - context 'with invalid notation' do - let(:input) { 'Foo' } - - it 'should return nil' do - should be(nil) - end - end -end diff --git a/spec/unit/mutant/matcher/namespace/each_spec.rb b/spec/unit/mutant/matcher/namespace/each_spec.rb new file mode 100644 index 00000000..57f0d935 --- /dev/null +++ b/spec/unit/mutant/matcher/namespace/each_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Mutant::Matcher::Namespace, '#each' do + subject { object.each { |item| yields << item } } + + let(:yields) { [] } + let(:object) { described_class.new(TestApp::Literal) } + + let(:singleton_a) { mock('SingletonA', :name => 'TestApp::Literal') } + let(:singleton_b) { mock('SingletonB', :name => 'TestApp::Foo') } + let(:subject_a) { mock('SubjectA') } + let(:subject_b) { mock('SubjectB') } + + before do + Mutant::Matcher::Methods::Singleton.stub(:each).with(singleton_a).and_yield(subject_a) + Mutant::Matcher::Methods::Instance.stub(:each).with(singleton_a).and_yield(subject_b) + ObjectSpace.stub(:each_object => [singleton_a, singleton_b]) + end + + it_should_behave_like 'an #each method' + + it 'should yield subjects' do + expect { subject }.to change { yields }.from([]).to([subject_a, subject_b]) + end +end diff --git a/spec/unit/mutant/matcher/object_space/class_methods/parse_spec.rb b/spec/unit/mutant/matcher/object_space/class_methods/parse_spec.rb deleted file mode 100644 index 0886d7aa..00000000 --- a/spec/unit/mutant/matcher/object_space/class_methods/parse_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe Mutant::Matcher::ObjectSpace, '.parse' do - subject { object.parse(input) } - - let(:object) { described_class } - - let(:matcher) { mock('Matcher') } - - context 'with valid notation' do - let(:input) { '::TestApp::Literal' } - - it 'should return matcher' do - described_class.should_receive(:new).with(%r(\ATestApp::Literal(\z|::))).and_return(matcher) - should be(matcher) - end - end - - context 'with invalid notation' do - let(:input) { 'TestApp' } - - it { should be(nil) } - end -end diff --git a/spec/unit/mutant/matcher/object_space/each_spec.rb b/spec/unit/mutant/matcher/object_space/each_spec.rb deleted file mode 100644 index 0be85070..00000000 --- a/spec/unit/mutant/matcher/object_space/each_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -describe Mutant::Matcher::ObjectSpace, '#each' do - before do - pending "defunct" - end - subject { object.each { |item| yields << item } } - - let(:yields) { [] } - let(:object) { described_class.new(/\ATestApp::Literal(\z|::)/) } - - before do - Mutant::Matcher::Method::Singleton.stub(:each).and_yield(matcher_a) - Mutant::Matcher::Method::Instance.stub(:each).and_yield(matcher_b) - matcher_a.stub(:each).and_yield(subject_a) - matcher_b.stub(:each).and_yield(subject_b) - end - - - let(:matcher_a) { mock('Matcher A') } - let(:matcher_b) { mock('Matcher B') } - - let(:subject_a) { mock('Subject A') } - let(:subject_b) { mock('Subject B') } - - it_should_behave_like 'an #each method' - - it 'should yield subjects' do - expect { subject }.to change { yields }.from([]).to([subject_a, subject_b]) - end -end From 19989f8766af048fa5d17c4a34f9fcd3a5e1432f Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 22:59:50 +0100 Subject: [PATCH 29/51] Equalizer Mutant::Runner on config --- lib/mutant/runner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index d9895332..551e91d0 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -1,7 +1,7 @@ module Mutant # Runner that allows to mutate an entire project class Runner - include Adamantium::Flat + include Adamantium::Flat, Equalizer.new(:config) extend MethodObject # Test for failure From 9ae26500a8b587ee32f6d9dcdb73ea1ce629da8b Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 23:13:34 +0100 Subject: [PATCH 30/51] Reconnect cli reporter with kill reporting --- lib/mutant/reporter/cli.rb | 45 +++++++++++++++--------------------- lib/mutant/reporter/stats.rb | 2 +- lib/mutant/runner.rb | 3 ++- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/lib/mutant/reporter/cli.rb b/lib/mutant/reporter/cli.rb index c5f7ded2..f726c595 100644 --- a/lib/mutant/reporter/cli.rb +++ b/lib/mutant/reporter/cli.rb @@ -84,36 +84,28 @@ module Mutant puts message.join("\n") end - ## Reporter killer - ## - ## @param [Killer] killer - ## - ## @return [self] - ## - ## @api private - ## - #def killer(killer) - # super - - # status = killer.killed? ? 'Killed' : 'Alive' - # color = killer.success? ? Color::GREEN : Color::RED - - # puts(colorize(color, "#{status}: #{killer.identification} (%02.2fs)" % killer.runtime)) - - # unless killer.success? - # colorized_diff(killer.mutation) - # end - - # self - #end - - # Return stats + # Report killer # - # @return [Stats] + # @param [Killer] killer + # + # @return [self] # # @api private # - attr_reader :stats + def report_killer(killer) + super + + status = killer.killed? ? 'Killed' : 'Alive' + color = killer.success? ? Color::GREEN : Color::RED + + puts(colorize(color, "#{status}: #{killer.identification} (%02.2fs)" % killer.runtime)) + + unless killer.success? + colorized_diff(killer.mutation) + end + + self + end private @@ -185,7 +177,6 @@ module Mutant differ = Differ.new(original, current) diff = color? ? differ.colorized_diff : differ.diff - # FIXME remove this branch before release if diff.empty? raise 'Unable to create a diff, so ast mutation or to_source has an error!' end diff --git a/lib/mutant/reporter/stats.rb b/lib/mutant/reporter/stats.rb index d9d6c3ac..827c301b 100644 --- a/lib/mutant/reporter/stats.rb +++ b/lib/mutant/reporter/stats.rb @@ -120,7 +120,7 @@ module Mutant # otherwise # def errors? - @killers.values.inject(0) do |fails, counter| + !!@killers.values.inject(0) do |fails, counter| fails + counter.fails end.nonzero? end diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index 551e91d0..022bf77c 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -15,7 +15,7 @@ module Mutant # @api private # def fail? - reporter.errors? + !reporter.empty? && reporter.errors? end # Return config @@ -76,6 +76,7 @@ module Mutant run_subject(subject) end util.teardown + reporter.print_stats end # Run mutation killers on subject From 7dfb785ed353c24f4c4f12d5fd40787f2b2dca0f Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 23:53:25 +0100 Subject: [PATCH 31/51] Test for success and not for fail --- lib/mutant/cli.rb | 3 +-- lib/mutant/reporter.rb | 36 ++++++++++++++++++++++++++++++++++++ lib/mutant/reporter/stats.rb | 12 ++++++++++++ lib/mutant/runner.rb | 10 +++++----- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index 9b5dcdb2..696120aa 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -32,8 +32,7 @@ module Mutant # @api private # def self.run(*arguments) - error = Runner.run(new(*arguments)).fail? - error ? EXIT_FAILURE : EXIT_SUCCESS + Runner.run(new(*arguments)).success? ? EXIT_SUCCESS : EXIT_FAILURE rescue Error => exception $stderr.puts(exception.message) EXIT_FAILURE diff --git a/lib/mutant/reporter.rb b/lib/mutant/reporter.rb index 373d9dcb..c1a92029 100644 --- a/lib/mutant/reporter.rb +++ b/lib/mutant/reporter.rb @@ -14,6 +14,42 @@ module Mutant @config = config end + # Test for success + # + # @return [true] + # if there are subjects and no errors + # + # @return [false] + # otherwise + # + # @api private + # + def success? + stats.success? + end + + # Report start + # + # @param [Config] config + # + # @return [self] + # + # @api private + # + def start(_config) + self + end + + # Report stop + # + # @return [self] + # + # @api private + # + def stop + self + end + # Report subject # # @param [Subject] subject diff --git a/lib/mutant/reporter/stats.rb b/lib/mutant/reporter/stats.rb index 827c301b..31bd100a 100644 --- a/lib/mutant/reporter/stats.rb +++ b/lib/mutant/reporter/stats.rb @@ -97,6 +97,18 @@ module Mutant @counts[:subject] += 1 end + # Test for success? + # + # @return [true] + # if there are subjects and no errors + # + # @return [false] + # otherwise + # + def success? + @counts[:subject].nonzero? && !errors? + end + # Count killer # # @param [Killer] killer diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index 022bf77c..2d0481ea 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -4,18 +4,18 @@ module Mutant include Adamantium::Flat, Equalizer.new(:config) extend MethodObject - # Test for failure + # Test for succcess # # @return [true] - # returns true when there are left mutations + # when there are subjects and no failures # # @return [false] - # returns false othewise + # otherwise # # @api private # - def fail? - !reporter.empty? && reporter.errors? + def success? + reporter.success? end # Return config From 5b3d506523ab3953090370a273fea5cc89ad5d8c Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 21 Jan 2013 23:54:25 +0100 Subject: [PATCH 32/51] Reconnect API changes for successful spec run Sorry this looks like spiking around and yeah it was some kind of. --- lib/mutant/context/scope.rb | 22 +++++++-------- lib/mutant/killer/rspec.rb | 6 ++-- lib/mutant/reporter.rb | 5 ++-- lib/mutant/reporter/cli.rb | 15 ++++++++-- lib/mutant/runner.rb | 4 +-- lib/mutant/strategy/rspec/dm2.rb | 6 ++-- lib/mutant/strategy/rspec/dm2/lookup.rb | 2 +- .../strategy/rspec/dm2/lookup/method.rb | 6 ++-- lib/mutant/subject/method.rb | 28 +++++++++++++++++-- .../unit/mutant/cli/class_methods/run_spec.rb | 9 +++--- .../lookup/method/instance/spec_files_spec.rb | 10 +++---- .../method/singleton/spec_files_spec.rb | 10 +++---- 12 files changed, 79 insertions(+), 44 deletions(-) diff --git a/lib/mutant/context/scope.rb b/lib/mutant/context/scope.rb index a5af3690..f51e0a5c 100644 --- a/lib/mutant/context/scope.rb +++ b/lib/mutant/context/scope.rb @@ -76,6 +76,16 @@ module Mutant name_nesting.last end + # Return name + # + # @return [String] + # + # @api private + # + def name + scope.name + end + # Return scope wrapped by context # # @return [::Module|::Class] @@ -105,17 +115,7 @@ module Mutant # @api private # def root_ast - "#{keyword} #{qualified_name}; end".to_ast - end - - # Return qualified name of scope - # - # @return [String] - # - # @api private - # - def qualified_name - scope.name + "#{keyword} #{name}; end".to_ast end # Return nesting of names of scope diff --git a/lib/mutant/killer/rspec.rb b/lib/mutant/killer/rspec.rb index c15160a9..6ff447f9 100644 --- a/lib/mutant/killer/rspec.rb +++ b/lib/mutant/killer/rspec.rb @@ -10,16 +10,16 @@ module Mutant # Run rspec test # # @return [true] - # returns true when test is NOT successful and the mutant was killed + # when test is NOT successful and the mutant was killed # # @return [false] - # returns false otherwise + # otherwise # # @api private # def run mutation.insert - !::RSpec::Core::Runner.run(command_line_arguments, strategy.error_stream, strategy.output_stream).zero? + !!::RSpec::Core::Runner.run(command_line_arguments, strategy.error_stream, strategy.output_stream).nonzero? end memoize :run diff --git a/lib/mutant/reporter.rb b/lib/mutant/reporter.rb index c1a92029..505f697b 100644 --- a/lib/mutant/reporter.rb +++ b/lib/mutant/reporter.rb @@ -58,7 +58,7 @@ module Mutant # # @api private # - def subject(subject) + def subject(_subject) stats.count_subject self end @@ -71,7 +71,7 @@ module Mutant # # @api private # - def mutation(mutation) + def mutation(_mutation) self end @@ -85,7 +85,6 @@ module Mutant # def report_killer(killer) stats.count_killer(killer) - self end diff --git a/lib/mutant/reporter/cli.rb b/lib/mutant/reporter/cli.rb index 52ee6576..4890b657 100644 --- a/lib/mutant/reporter/cli.rb +++ b/lib/mutant/reporter/cli.rb @@ -67,7 +67,7 @@ module Mutant self end - # Report config + # Report start # # @param [Mutant::Config] config # @@ -75,13 +75,24 @@ module Mutant # # @api private # - def print_config + def start(config) message = [] message << 'Mutant configuration:' message << "Matcher: #{config.matcher.inspect}" message << "Filter: #{config.filter.inspect}" message << "Strategy: #{config.strategy.inspect}" puts message.join("\n") + super + end + + # Report stop + # + # @return [self] + # + # @api private + # + def stop + super end # Report killer diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index 2d0481ea..fba638ce 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -68,7 +68,7 @@ module Mutant # @api private # def run - reporter.print_config + reporter.start(config) util = strategy util.setup config.matcher.each do |subject| @@ -76,7 +76,7 @@ module Mutant run_subject(subject) end util.teardown - reporter.print_stats + reporter.stop end # Run mutation killers on subject diff --git a/lib/mutant/strategy/rspec/dm2.rb b/lib/mutant/strategy/rspec/dm2.rb index e5fa5915..4e6f8c45 100644 --- a/lib/mutant/strategy/rspec/dm2.rb +++ b/lib/mutant/strategy/rspec/dm2.rb @@ -6,12 +6,14 @@ module Mutant # Return filename pattern # + # @param [Subject] subject + # # @return [Enumerable] # # @api private # - def spec_files(mutation) - ExampleLookup.run(mutation) + def spec_files(subject) + Lookup.run(subject) end end diff --git a/lib/mutant/strategy/rspec/dm2/lookup.rb b/lib/mutant/strategy/rspec/dm2/lookup.rb index ba9d2866..21e94662 100644 --- a/lib/mutant/strategy/rspec/dm2/lookup.rb +++ b/lib/mutant/strategy/rspec/dm2/lookup.rb @@ -42,7 +42,7 @@ module Mutant # @api private # def self.run(subject) - new(subject).spec_files + build(subject).spec_files end REGISTRY = {} diff --git a/lib/mutant/strategy/rspec/dm2/lookup/method.rb b/lib/mutant/strategy/rspec/dm2/lookup/method.rb index 03400e1f..69e4066a 100644 --- a/lib/mutant/strategy/rspec/dm2/lookup/method.rb +++ b/lib/mutant/strategy/rspec/dm2/lookup/method.rb @@ -25,7 +25,7 @@ module Mutant # @api private # def base_path - "spec/unit/#{Inflector.underscore(subject.context_name)}" + "spec/unit/#{Inflector.underscore(subject.context.name)}" end # Return method name @@ -35,7 +35,7 @@ module Mutant # @api private # def method_name - subject.method_name + subject.name end # Test if method is public @@ -58,7 +58,7 @@ module Mutant # @api private # def expanded_name - MethodExpansion.run(subject.method_name) + MethodExpansion.run(method_name) end # Return glob expression diff --git a/lib/mutant/subject/method.rb b/lib/mutant/subject/method.rb index 8d3d106e..8c980a82 100644 --- a/lib/mutant/subject/method.rb +++ b/lib/mutant/subject/method.rb @@ -15,6 +15,28 @@ module Mutant # abstract_method :public? + # Return method name + # + # @return [Symbol] + # + # @api private + # + def name + node.name + end + + private + + # Return scope + # + # @return [Class, Module] + # + # @api private + # + def scope + context.scope + end + # Instance method subjects class Instance < self @@ -29,7 +51,7 @@ module Mutant # @api private # def public? - scope.public_method_defined?(method_name) + scope.public_method_defined?(name) end memoize :public? @@ -42,7 +64,7 @@ module Mutant # @api private # def subtype - "#{context.identification}##{node.name}" + "#{context.identification}##{name}" end end @@ -61,7 +83,7 @@ module Mutant # @api private # def public? - scope.singleton_class.public_method_defined?(method_name) + scope.singleton_class.public_method_defined?(name) end memoize :public? diff --git a/spec/unit/mutant/cli/class_methods/run_spec.rb b/spec/unit/mutant/cli/class_methods/run_spec.rb index 633d4572..2528611a 100644 --- a/spec/unit/mutant/cli/class_methods/run_spec.rb +++ b/spec/unit/mutant/cli/class_methods/run_spec.rb @@ -6,7 +6,7 @@ describe Mutant::CLI, '.run' do let(:object) { described_class } let(:argv) { mock('ARGV') } let(:attributes) { mock('Options') } - let(:runner) { mock('Runner', :fail? => failure) } + let(:runner) { mock('Runner', :success? => success) } let(:instance) { mock(described_class.name, :attributes => attributes) } before do @@ -14,8 +14,8 @@ describe Mutant::CLI, '.run' do Mutant::Runner.stub(:run => runner) end - context 'when runner does NOT fail' do - let(:failure) { false } + context 'when runner is successful' do + let(:success) { true } it { should be(0) } @@ -26,7 +26,7 @@ describe Mutant::CLI, '.run' do end context 'when runner fails' do - let(:failure) { true } + let(:success) { false } it { should be(1) } @@ -35,4 +35,5 @@ describe Mutant::CLI, '.run' do should be(1) end end + end diff --git a/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb index 5f1bbde1..6fdbca51 100644 --- a/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb +++ b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb @@ -3,11 +3,11 @@ require 'spec_helper' describe Mutant::Strategy::Rspec::DM2::Lookup::Method::Instance, '#spec_files' do subject { object.spec_files } - let(:object) { described_class.new(mutation_subject) } - let(:mutation_subject) { mock('Subject', :method_name => method_name, :public? => is_public, :context_name => context_name) } - let(:context_name) { 'Foo' } - let(:method_name) { :bar } - let(:files) { 'Files'.freeze } + let(:object) { described_class.new(mutation_subject) } + let(:mutation_subject) { mock('Subject', :name => method_name, :public? => is_public, :context => context) } + let(:context) { mock('Context', :name => 'Foo') } + let(:method_name) { :bar } + let(:files) { 'Files'.freeze } this_example_group = 'Mutant::Strategy::Rspec::DM2::Lookup::Method::Instance#spec_files' diff --git a/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb index 573d85eb..a576f10e 100644 --- a/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb +++ b/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb @@ -4,11 +4,11 @@ describe Mutant::Strategy::Rspec::DM2::Lookup::Method::Singleton, '#spec_files' subject { object.spec_files } - let(:object) { described_class.new(mutation_subject) } - let(:mutation_subject) { mock('Subject', :method_name => method_name, :public? => is_public, :context_name => context_name) } - let(:context_name) { 'Foo' } - let(:method_name) { :bar } - let(:files) { 'Files'.freeze } + let(:object) { described_class.new(mutation_subject) } + let(:mutation_subject) { mock('Subject', :name => method_name, :public? => is_public, :context => context) } + let(:method_name) { :bar } + let(:files) { 'Files'.freeze } + let(:context) { mock('Context', :name => 'Foo') } this_example_group = 'Mutant::Strategy::Rspec::DM2::Lookup::Method::Singleton#spec_files' From f111967df7aad25ebf58d97d8802b45c155ee09b Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Tue, 22 Jan 2013 16:05:56 +0100 Subject: [PATCH 33/51] Use inflecto instead of mbj-inflector --- lib/mutant.rb | 2 +- lib/mutant/strategy/rspec/dm2/lookup/method.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mutant.rb b/lib/mutant.rb index 8399cf94..62e2d1f7 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -7,7 +7,7 @@ require 'descendants_tracker' require 'securerandom' require 'equalizer' require 'digest/sha1' -require 'inflector' +require 'inflecto' require 'to_source' require 'ice_nine' require 'diff/lcs' diff --git a/lib/mutant/strategy/rspec/dm2/lookup/method.rb b/lib/mutant/strategy/rspec/dm2/lookup/method.rb index 69e4066a..bcb94d94 100644 --- a/lib/mutant/strategy/rspec/dm2/lookup/method.rb +++ b/lib/mutant/strategy/rspec/dm2/lookup/method.rb @@ -25,7 +25,7 @@ module Mutant # @api private # def base_path - "spec/unit/#{Inflector.underscore(subject.context.name)}" + "spec/unit/#{Inflecto.underscore(subject.context.name)}" end # Return method name From 80223c6f57b5b65ed8974a5ff5862436b597e5e0 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Thu, 24 Jan 2013 14:03:26 +0100 Subject: [PATCH 34/51] Remove vendored tasks, they are already shipped by devools --- tasks/metrics/ci.rake | 7 -- tasks/metrics/flay.rake | 41 ------- tasks/metrics/flog.rake | 43 ------- tasks/metrics/heckle.rake | 216 ----------------------------------- tasks/metrics/metric_fu.rake | 31 ----- tasks/metrics/reek.rake | 15 --- tasks/metrics/roodi.rake | 15 --- tasks/metrics/yardstick.rake | 23 ---- tasks/spec.rake | 45 -------- tasks/yard.rake | 9 -- 10 files changed, 445 deletions(-) delete mode 100644 tasks/metrics/ci.rake delete mode 100644 tasks/metrics/flay.rake delete mode 100644 tasks/metrics/flog.rake delete mode 100644 tasks/metrics/heckle.rake delete mode 100644 tasks/metrics/metric_fu.rake delete mode 100644 tasks/metrics/reek.rake delete mode 100644 tasks/metrics/roodi.rake delete mode 100644 tasks/metrics/yardstick.rake delete mode 100644 tasks/spec.rake delete mode 100644 tasks/yard.rake diff --git a/tasks/metrics/ci.rake b/tasks/metrics/ci.rake deleted file mode 100644 index e28b262d..00000000 --- a/tasks/metrics/ci.rake +++ /dev/null @@ -1,7 +0,0 @@ -desc 'Run metrics with Heckle' -task :ci => %w[ ci:metrics heckle ] - -namespace :ci do - desc 'Run metrics' - task :metrics => %w[ verify_measurements flog flay reek roodi metrics:all ] -end diff --git a/tasks/metrics/flay.rake b/tasks/metrics/flay.rake deleted file mode 100644 index 1d0f6dc5..00000000 --- a/tasks/metrics/flay.rake +++ /dev/null @@ -1,41 +0,0 @@ -begin - require 'flay' - require 'yaml' - - config = YAML.load_file(File.expand_path('../../../config/flay.yml', __FILE__)).freeze - threshold = config.fetch('threshold').to_i - total_score = config.fetch('total_score').to_f - files = Flay.expand_dirs_to_files(config.fetch('path', 'lib')) - - # original code by Marty Andrews: - # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html - desc 'Analyze for code duplication' - task :flay do - # run flay once without a threshold to ensure the max mass matches the threshold - flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0) - flay.process(*files) - - max = flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max - unless max >= threshold - raise "Adjust flay threshold down to #{max}" - end - - total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) } - unless total == total_score - raise "Flay total is now #{total}, but expected #{total_score}" - end - - # run flay a second time with the threshold set - flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ) - flay.process(*files) - - if flay.masses.any? - flay.report - raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}" - end - end -rescue LoadError - task :flay do - abort 'Flay is not available. In order to run flay, you must: gem install flay' - end -end diff --git a/tasks/metrics/flog.rake b/tasks/metrics/flog.rake deleted file mode 100644 index 4f1b69e8..00000000 --- a/tasks/metrics/flog.rake +++ /dev/null @@ -1,43 +0,0 @@ -begin - require 'flog' - require 'yaml' - - class Float - def round_to(n) - (self * 10**n).round.to_f * 10**-n - end - end - - config = YAML.load_file(File.expand_path('../../../config/flog.yml', __FILE__)).freeze - threshold = config.fetch('threshold').to_f.round_to(1) - - # original code by Marty Andrews: - # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html - desc 'Analyze for code complexity' - task :flog do - flog = Flog.new - flog.flog Array(config.fetch('path', 'lib')) - - totals = flog.totals.select { |name, score| name[-5, 5] != '#none' }. - map { |name, score| [ name, score.round_to(1) ] }. - sort_by { |name, score| score } - - max = totals.last[1] - unless max >= threshold - raise "Adjust flog score down to #{max}" - end - - bad_methods = totals.select { |name, score| score > threshold } - if bad_methods.any? - bad_methods.reverse_each do |name, score| - puts '%8.1f: %s' % [ score, name ] - end - - raise "#{bad_methods.size} methods have a flog complexity > #{threshold}" - end - end -rescue LoadError - task :flog do - abort 'Flog is not available. In order to run flog, you must: gem install flog' - end -end diff --git a/tasks/metrics/heckle.rake b/tasks/metrics/heckle.rake deleted file mode 100644 index 4e56db62..00000000 --- a/tasks/metrics/heckle.rake +++ /dev/null @@ -1,216 +0,0 @@ -$LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__)) - -# original code by Ashley Moran: -# http://aviewfromafar.net/2007/11/1/rake-task-for-heckling-your-specs - -begin - require 'pathname' - require 'backports' - require 'active_support/inflector' - require 'heckle' - require 'mspec' - require 'mspec/utils/name_map' - - SKIP_METHODS = %w[ blank_slate_method_added ].freeze - - class NameMap - def file_name(method, constant) - map = MAP[method] - name = if map - map[constant] || map[:default] - else - method. - gsub('?','_ques'). - gsub('!','_bang'). - gsub('=','_assign') - end - "#{name}_spec.rb" - end - end - - desc 'Heckle each module and class' - task :heckle do - unless Ruby2Ruby::VERSION == '1.2.2' - raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle" - end - - require File.expand_path('../../../spec/support/fake_ast',__FILE__) - require 'mutant' - - root_module_regexp = Regexp.union('Mutant') - - spec_dir = Pathname('spec/unit') - - NameMap::MAP.each do |op, method| - next if method.kind_of?(Hash) - NameMap::MAP[op] = { :default => method } - end - - aliases = Hash.new { |h,mod| h[mod] = Hash.new { |h,method| h[method] = method } } - map = NameMap.new - - heckle_caught_modules = Hash.new { |hash, key| hash[key] = [] } - unhandled_mutations = 0 - - ObjectSpace.each_object(Module) do |mod| - next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/ - - # Mutation::Loader is to rbx specific - next if mod == Mutant::Loader - # Mutation::Matcher::Method is to rbx specific - next if mod == Mutant::Matcher::Method - # Mutation::Context::Constant is to rbx specific - next if mod == Mutant::Context::Constant - - spec_prefix = spec_dir.join(mod.name.underscore) - - specs = [] - - # get the public class methods - metaclass = class << mod; self end - ancestors = metaclass.ancestors - - spec_class_methods = mod.singleton_methods(false) - - spec_class_methods.reject! do |method| - %w[ yaml_new yaml_tag_subclasses? included nesting constants ].include?(method.to_s) - end - - if mod.ancestors.include?(Singleton) - spec_class_methods.reject! { |method| method.to_s == 'instance' } - end - - # get the protected and private class methods - other_class_methods = metaclass.protected_instance_methods(false) | - metaclass.private_instance_methods(false) - - ancestors.each do |ancestor| - other_class_methods -= ancestor.protected_instance_methods(false) | - ancestor.private_instance_methods(false) - end - - other_class_methods.reject! do |method| - method.to_s == 'allocate' || SKIP_METHODS.include?(method.to_s) - end - - other_class_methods.reject! do |method| - next unless spec_class_methods.any? { |specced| specced.to_s == $1 } - - spec_class_methods << method - end - - # get the instances methods - spec_methods = mod.public_instance_methods(false) - - other_methods = mod.protected_instance_methods(false) | - mod.private_instance_methods(false) - - other_methods.reject! do |method| - next unless spec_methods.any? { |specced| specced.to_s == $1 } - - spec_methods << method - end - - # map the class methods to spec files - spec_class_methods.each do |method| - method = aliases[mod.name][method] - next if SKIP_METHODS.include?(method.to_s) - - spec_file = spec_prefix.join('class_methods').join(map.file_name(method, mod.name)) - - unless spec_file.file? - raise "No spec file #{spec_file} for #{mod}.#{method}" - next - end - - specs << [ ".#{method}", [ spec_file ] ] - end - - # map the instance methods to spec files - spec_methods.each do |method| - method = aliases[mod.name][method] - next if SKIP_METHODS.include?(method.to_s) - - spec_file = spec_prefix.join(map.file_name(method, mod.name)) - - unless spec_file.file? - raise "No spec file #{spec_file} for #{mod}##{method}" - next - end - - specs << [ "##{method}", [ spec_file ] ] - end - - # non-public methods are considered covered if they can be mutated - # and any spec fails for the current or descendant modules - other_methods.each do |method| - descedant_specs = [] - - ObjectSpace.each_object(Module) do |descedant| - next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant - descedant_spec_prefix = spec_dir.join(descedant.name.underscore) - descedant_specs << descedant_spec_prefix - - if method.to_s == 'initialize' - descedant_specs.concat(Pathname.glob(descedant_spec_prefix.join('class_methods/new_spec.rb'))) - end - end - - specs << [ "##{method}", descedant_specs ] - end - - other_class_methods.each do |method| - descedant_specs = [] - - ObjectSpace.each_object(Module) do |descedant| - next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant - descedant_specs << spec_dir.join(descedant.name.underscore).join('class_methods') - end - - specs << [ ".#{method}", descedant_specs ] - end - - specs.sort.each do |(method, spec_files)| - puts "Heckling #{mod}#{method}" - IO.popen("spec #{spec_files.join(' ')} --heckle '#{mod}#{method}'") do |pipe| - while line = pipe.gets - case line = line.chomp - when "The following mutations didn't cause test failures:" - heckle_caught_modules[mod.name] << method - when '+++ mutation' - unhandled_mutations += 1 - end - end - end - end - end - - if unhandled_mutations > 0 - error_message_lines = [ "*************\n" ] - - error_message_lines << "Heckle found #{unhandled_mutations} " \ - "mutation#{"s" unless unhandled_mutations == 1} " \ - "that didn't cause spec violations\n" - - heckle_caught_modules.each do |mod, methods| - error_message_lines << "#{mod} contains the following " \ - 'poorly-specified methods:' - methods.each do |method| - error_message_lines << " - #{method}" - end - error_message_lines << '' - end - - error_message_lines << 'Get your act together and come back ' \ - 'when your specs are doing their job!' - - raise error_message_lines.join("\n") - else - puts 'Well done! Your code withstood a heckling.' - end - end -rescue LoadError - task :heckle => :spec do - $stderr.puts 'Heckle or mspec is not available. In order to run heckle, you must: gem install heckle mspec' - end -end diff --git a/tasks/metrics/metric_fu.rake b/tasks/metrics/metric_fu.rake deleted file mode 100644 index d9814b3f..00000000 --- a/tasks/metrics/metric_fu.rake +++ /dev/null @@ -1,31 +0,0 @@ -begin - # Require veritas before metric foo pulls AS - require 'veritas' - require 'metric_fu' - require 'json' - - # XXX: temporary hack until metric_fu is fixed - MetricFu::Saikuro.class_eval { include FileUtils } - - MetricFu::Configuration.run do |config| - config.rcov = { - :environment => 'test', - :test_files => %w[ spec/**/*_spec.rb ], - :rcov_opts => %w[ - --sort coverage - --no-html - --text-coverage - --no-color - --profile - --exclude spec/,^/ - --include lib:spec - ], - } - end -rescue LoadError - namespace :metrics do - task :all do - $stderr.puts 'metric_fu is not available. In order to run metrics:all, you must: gem install metric_fu' - end - end -end diff --git a/tasks/metrics/reek.rake b/tasks/metrics/reek.rake deleted file mode 100644 index b9a1bad3..00000000 --- a/tasks/metrics/reek.rake +++ /dev/null @@ -1,15 +0,0 @@ -begin - require 'reek/rake/task' - - if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx' - task :reek do - $stderr.puts 'Reek fails under rubinius, fix rubinius and remove guard' - end - else - Reek::Rake::Task.new - end -rescue LoadError - task :reek do - $stderr.puts 'Reek is not available. In order to run reek, you must: gem install reek' - end -end diff --git a/tasks/metrics/roodi.rake b/tasks/metrics/roodi.rake deleted file mode 100644 index 29b4616e..00000000 --- a/tasks/metrics/roodi.rake +++ /dev/null @@ -1,15 +0,0 @@ -begin - require 'roodi' - require 'rake/tasklib' - require 'roodi_task' - - RoodiTask.new do |t| - t.verbose = false - t.config = File.expand_path('../../../config/roodi.yml', __FILE__) - t.patterns = %w[ lib/**/*.rb ] - end -rescue LoadError - task :roodi do - abort 'Roodi is not available. In order to run roodi, you must: gem install roodi' - end -end diff --git a/tasks/metrics/yardstick.rake b/tasks/metrics/yardstick.rake deleted file mode 100644 index 358cbf36..00000000 --- a/tasks/metrics/yardstick.rake +++ /dev/null @@ -1,23 +0,0 @@ -begin - require 'pathname' - require 'yardstick' - require 'yardstick/rake/measurement' - require 'yardstick/rake/verify' - require 'yaml' - - config = YAML.load_file(File.expand_path('../../../config/yardstick.yml', __FILE__)) - - # yardstick_measure task - Yardstick::Rake::Measurement.new - - # verify_measurements task - Yardstick::Rake::Verify.new do |verify| - verify.threshold = config.fetch('threshold') - end -rescue LoadError - %w[ yardstick_measure verify_measurements ].each do |name| - task name.to_s do - abort "Yardstick is not available. In order to run #{name}, you must: gem install yardstick" - end - end -end diff --git a/tasks/spec.rake b/tasks/spec.rake deleted file mode 100644 index 4ce92c06..00000000 --- a/tasks/spec.rake +++ /dev/null @@ -1,45 +0,0 @@ -begin - - begin - require 'rspec/core/rake_task' - rescue LoadError - require 'spec/rake/spectask' - - module RSpec - module Core - RakeTask = Spec::Rake::SpecTask - end - end - end - - desc 'run all specs' - task :spec => %w[ spec:unit spec:integration ] - - namespace :spec do - RSpec::Core::RakeTask.new(:integration) do |t| - t.pattern = 'spec/integration/**/*_spec.rb' - end - - RSpec::Core::RakeTask.new(:unit) do |t| - t.pattern = 'spec/unit/**/*_spec.rb' - end - end -rescue LoadError - task :spec do - abort 'rspec is not available. In order to run spec, you must: gem install rspec' - end -end - -begin - desc "Generate code coverage" - RSpec::Core::RakeTask.new(:rcov) do |t| - t.rcov = true - t.rcov_opts = File.read('spec/rcov.opts').split(/\s+/) - end -rescue LoadError - task :rcov do - abort 'rcov is not available. In order to run rcov, you must: gem install rcov' - end -end - -task :test => 'spec' diff --git a/tasks/yard.rake b/tasks/yard.rake deleted file mode 100644 index a2768706..00000000 --- a/tasks/yard.rake +++ /dev/null @@ -1,9 +0,0 @@ -begin - require 'yard' - - YARD::Rake::YardocTask.new -rescue LoadError - task :yard do - abort 'YARD is not available. In order to run yard, you must: gem install yard' - end -end From 3ec1c6b31dd8d8f56786725bfd68cef514ef0f77 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Thu, 24 Jan 2013 20:41:11 +0100 Subject: [PATCH 35/51] Bump version to 0.3.0 --- mutant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mutant.gemspec b/mutant.gemspec index e0771bce..53e032f9 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency('descendants_tracker', '~> 0.0.1') gem.add_runtime_dependency('backports', '~> 2.7.0') gem.add_runtime_dependency('adamantium', '~> 0.0.5') - gem.add_runtime_dependency('mbj-inflector', '~> 0.0.1') + gem.add_runtime_dependency('inflecto', '~> 0.0.1') gem.add_runtime_dependency('equalizer', '~> 0.0.1') gem.add_runtime_dependency('abstract_type', '~> 0.0.2') gem.add_runtime_dependency('diff-lcs', '~> 1.1.3') From be14fd50aac16d780ce67aa7d9723458c4057e46 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 26 Jan 2013 17:35:42 +0100 Subject: [PATCH 36/51] Use regexp literal that can be generated correctly by to_source Later versions of to_source that are backed by regexp_parser will emit this correcty. --- lib/mutant/matcher/method.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mutant/matcher/method.rb b/lib/mutant/matcher/method.rb index c3daa845..88e8fafe 100644 --- a/lib/mutant/matcher/method.rb +++ b/lib/mutant/matcher/method.rb @@ -6,7 +6,7 @@ module Mutant # Methods within rbx kernel directory are precompiled and their source # cannot be accessed via reading source location - BLACKLIST = %r(\Akernel/).freeze + BLACKLIST = /\Akernel\//.freeze # Enumerate matches # From 41130fe6dc91be4a35cf7f4122ac1b28f53c5037 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 26 Jan 2013 17:49:58 +0100 Subject: [PATCH 37/51] Bump diff-lcs ~> 1.2.0 --- mutant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mutant.gemspec b/mutant.gemspec index 586f88df..5144eefd 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -23,5 +23,5 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency('inflecto', '~> 0.0.2') gem.add_runtime_dependency('equalizer', '~> 0.0.1') gem.add_runtime_dependency('abstract_type', '~> 0.0.2') - gem.add_runtime_dependency('diff-lcs', '~> 1.1.3') + gem.add_runtime_dependency('diff-lcs', '~> 1.2.0') end From a0fb636834ee2aa6e6d10ada858b7c32246d8821 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 26 Jan 2013 17:50:27 +0100 Subject: [PATCH 38/51] Bump to_source to ~> 0.2.17 --- mutant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mutant.gemspec b/mutant.gemspec index 5144eefd..c313dd2c 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |gem| gem.extra_rdoc_files = %w[TODO LICENSE] gem.executables = [ 'mutant' ] - gem.add_runtime_dependency('to_source', '~> 0.2.14') + gem.add_runtime_dependency('to_source', '~> 0.2.17') gem.add_runtime_dependency('ice_nine', '~> 0.6.0') gem.add_runtime_dependency('descendants_tracker', '~> 0.0.1') gem.add_runtime_dependency('backports', '~> 2.7.0') From 57c0f2ad433783a23204cb3f39afab2474de2bdc Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 26 Jan 2013 17:50:55 +0100 Subject: [PATCH 39/51] Bump backports to ~> 2.7.1 --- mutant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mutant.gemspec b/mutant.gemspec index c313dd2c..b7326581 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency('to_source', '~> 0.2.17') gem.add_runtime_dependency('ice_nine', '~> 0.6.0') gem.add_runtime_dependency('descendants_tracker', '~> 0.0.1') - gem.add_runtime_dependency('backports', '~> 2.7.0') + gem.add_runtime_dependency('backports', '~> 2.7.1') gem.add_runtime_dependency('adamantium', '~> 0.0.5') gem.add_runtime_dependency('inflecto', '~> 0.0.2') gem.add_runtime_dependency('equalizer', '~> 0.0.1') From 518ef3ed5d80419b66384c25feb897f3828047df Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 26 Jan 2013 17:51:20 +0100 Subject: [PATCH 40/51] Bump equalizer to ~> 0.0.3 --- mutant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mutant.gemspec b/mutant.gemspec index b7326581..31f1309f 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency('backports', '~> 2.7.1') gem.add_runtime_dependency('adamantium', '~> 0.0.5') gem.add_runtime_dependency('inflecto', '~> 0.0.2') - gem.add_runtime_dependency('equalizer', '~> 0.0.1') + gem.add_runtime_dependency('equalizer', '~> 0.0.3') gem.add_runtime_dependency('abstract_type', '~> 0.0.2') gem.add_runtime_dependency('diff-lcs', '~> 1.2.0') end From ba3368e9b672369b570f4c7c9aa50794c57ad8ce Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 26 Jan 2013 17:55:38 +0100 Subject: [PATCH 41/51] Revert "Bump diff-lcs ~> 1.2.0" This reverts commit 41130fe6dc91be4a35cf7f4122ac1b28f53c5037. * Rspec is not compatible with diff-lcs ~> 1.2.0 --- mutant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mutant.gemspec b/mutant.gemspec index 31f1309f..49dcc38d 100644 --- a/mutant.gemspec +++ b/mutant.gemspec @@ -23,5 +23,5 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency('inflecto', '~> 0.0.2') gem.add_runtime_dependency('equalizer', '~> 0.0.3') gem.add_runtime_dependency('abstract_type', '~> 0.0.2') - gem.add_runtime_dependency('diff-lcs', '~> 1.2.0') + gem.add_runtime_dependency('diff-lcs', '~> 1.1.3') end From 14cfc51e2c879ded27322d09758f8993f9078a78 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Fri, 1 Feb 2013 20:09:13 +0100 Subject: [PATCH 42/51] Update devtools --- Gemfile.devtools | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Gemfile.devtools b/Gemfile.devtools index 16f7a10c..2c3bcfd5 100644 --- a/Gemfile.devtools +++ b/Gemfile.devtools @@ -21,7 +21,7 @@ group :guard do gem 'rb-inotify', '~> 0.9.0', :require => false # Remove this one https://github.com/guard/listen/pull/78 is released - gem 'listen', '~> 0.7.2', :git => 'https://github.com/guard/listen' + gem 'listen', '~> 0.7.2', :git => 'https://github.com/guard/listen.git' # notification handling gem 'libnotify', '~> 0.8.0', :require => false @@ -35,17 +35,13 @@ group :metrics do gem 'flog', '~> 2.5.3' gem 'reek', '~> 1.2.13', :git => 'https://github.com/troessner/reek.git', :ref => 'ef77fcecaa21c9ebcbe4d9a79d41b0e70196bf18' gem 'roodi', '~> 2.1.0' - gem 'yardstick', '~> 0.9.0' + gem 'yardstick', '~> 0.9.1' platforms :ruby_18, :ruby_19 do # this indirectly depends on ffi which does not build on ruby-head gem 'yard-spellcheck', '~> 0.1.5' end - platforms :mri_18 do - gem 'rcov', '~> 1.0.0' - end - platforms :mri_19 do gem 'simplecov', '~> 0.7.1' end From 4089434110818697d15bcc9ca420d116ba50f187 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Fri, 1 Feb 2013 20:11:34 +0100 Subject: [PATCH 43/51] Remove unused class --- lib/mutant/strategy/rspec/example_lookup.rb | 163 -------------------- 1 file changed, 163 deletions(-) delete mode 100644 lib/mutant/strategy/rspec/example_lookup.rb diff --git a/lib/mutant/strategy/rspec/example_lookup.rb b/lib/mutant/strategy/rspec/example_lookup.rb deleted file mode 100644 index 825073fd..00000000 --- a/lib/mutant/strategy/rspec/example_lookup.rb +++ /dev/null @@ -1,163 +0,0 @@ -module Mutant - class Strategy - class Rspec - - # Example lookup for rspec - class ExampleLookup - include Adamantium::Flat, Equalizer.new(:mutation) - - # Perform example lookup - # - # @param [Mutation] mutation - # - # @return [Enumerable] - # - # @api private - # - def self.run(mutation) - new(mutation).spec_files - end - - # Return mutation - # - # @return [Mutation] - # - # @api private - # - attr_reader :mutation - - # Return spec files - # - # @return [Enumerable] - # - # @api private - # - def spec_files - expression = glob_expression - files = Dir[expression] - - if files.empty? - $stderr.puts("Spec file(s): #{expression.inspect} not found for #{mutation.identification}") - end - - files - end - memoize :spec_files - - private - - # Return method matcher - # - # @return [Matcher] - # - # @api private - # - def matcher - mutation.subject.matcher - end - - EXPANSIONS = { - /\?\z/ => '_predicate', - /=\z/ => '_writer', - /!\z/ => '_bang' - } - - # Return spec file - # - # @return [String] - # - # @api private - # - def spec_file - "#{mapped_name || expanded_name}_spec.rb" - end - memoize :spec_file - - # Return mapped name - # - # @return [Symbol] - # if name was mapped - # - # @return [nil] - # otherwise - # - # @api private - # - def mapped_name - OPERATOR_EXPANSIONS[method_name] - end - - # Return expanded name - # - # @return [Symbol] - # - # @api private - # - def expanded_name - EXPANSIONS.inject(method_name) do |name, (regexp, expansion)| - name.to_s.gsub(regexp, expansion) - end.to_sym - end - - # Return method name - # - # @return [Symbol] - # - # @api private - # - def method_name - matcher.method_name - end - - # Return glob expression - # - # @return [String] - # - # @api private - # - def glob_expression - base = base_path - - if mutation.subject.matcher.public? - "#{base}/#{spec_file}" - else - "#{base}/*_spec.rb" - end - end - - # Return instance of singleton path appendo - # - # @return [String] - # - # @api private - # - def scope_append - matcher.kind_of?(Matcher::Method::Singleton) ? '/class_methods' : '' - end - memoize :scope_append - - # Return base path - # - # @return [String] - # - # @api private - # - def base_path - "spec/unit/#{Inflecto.underscore(mutation.subject.context.scope.name)}#{scope_append}" - end - memoize :base_path - - # Initalize object - # - # @param [Mutation] mutation - # - # @api private - # - def initialize(mutation) - @mutation = mutation - end - - end - end - end -end From 6fe8b1181caca55447ce08cc0032eb513ffcd831 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Fri, 1 Feb 2013 20:20:43 +0100 Subject: [PATCH 44/51] Simplify method expansion --- lib/mutant/strategy/method_expansion.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mutant/strategy/method_expansion.rb b/lib/mutant/strategy/method_expansion.rb index 96844809..4dc90dbe 100644 --- a/lib/mutant/strategy/method_expansion.rb +++ b/lib/mutant/strategy/method_expansion.rb @@ -40,8 +40,8 @@ module Mutant # @api private # def self.expand(name) - METHOD_NAME_EXPANSIONS.inject(name) do |name, (regexp, expansion)| - name.to_s.gsub(regexp, expansion) + METHOD_NAME_EXPANSIONS.inject(name.to_s) do |name, find_replace| + name.gsub(*find_replace) end.to_sym end private_class_method :expand From 2b6470f2231d43578aa44b04cb9b5d5bb5cc6348 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Fri, 1 Feb 2013 23:06:07 +0100 Subject: [PATCH 45/51] Add Mutant::Runner::Subject The idea is to break up the big fat and ugly Runner class into dedicated pices with a single reponsibility. --- lib/mutant.rb | 2 + lib/mutant/runner.rb | 116 +----------------- lib/mutant/runner/subject.rb | 41 +++++++ .../runner/subject/class_methods/new_spec.rb | 27 ++++ 4 files changed, 74 insertions(+), 112 deletions(-) create mode 100644 lib/mutant/runner/subject.rb create mode 100644 spec/unit/mutant/runner/subject/class_methods/new_spec.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index 62e2d1f7..952a9bc3 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -107,6 +107,8 @@ require 'mutant/strategy/rspec/dm2' require 'mutant/strategy/rspec/dm2/lookup' require 'mutant/strategy/rspec/dm2/lookup/method' require 'mutant/runner' +require 'mutant/runner/subject' +require 'mutant/runner/mutation' require 'mutant/cli' require 'mutant/cli/classifier' require 'mutant/cli/classifier/namespace' diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index fba638ce..4359d238 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -1,22 +1,7 @@ module Mutant # Runner that allows to mutate an entire project class Runner - include Adamantium::Flat, Equalizer.new(:config) - extend MethodObject - - # Test for succcess - # - # @return [true] - # when there are subjects and no failures - # - # @return [false] - # otherwise - # - # @api private - # - def success? - reporter.success? - end + include Adamantium::Flat, AbstractType # Return config # @@ -26,8 +11,6 @@ module Mutant # attr_reader :config - private - # Initialize object # # @param [Config] config @@ -41,106 +24,15 @@ module Mutant run end - # Return strategy - # - # @return [Strategy] - # - # @api private - # - def strategy - config.strategy - end + private - # Return reporter - # - # @return [Reporter] - # - # @api private - # - def reporter - config.reporter - end - - # Run mutation killers on subjects + # Perform operation # # @return [undefined] # # @api private # - def run - reporter.start(config) - util = strategy - util.setup - config.matcher.each do |subject| - reporter.subject(subject) - run_subject(subject) - end - util.teardown - reporter.stop - end + abstract_method :run - # Run mutation killers on subject - # - # @param [Subject] subject - # - # @return [undefined] - # - # @api private - # - def run_subject(subject) - return unless test_noop(subject) - subject.each do |mutation| - next unless config.filter.match?(mutation) - reporter.mutation(mutation) - kill(mutation) - end - end - - # Test noop mutation - # - # @return [true] - # if noop mutation is alive - # - # @return [false] - # otherwise - # - # @api private - # - def test_noop(subject) - noop = subject.noop - unless kill(noop) - reporter.noop(noop) - return false - end - true - end - - # Run killer on mutation - # - # @param [Mutation] mutation - # - # @return [true] - # if killer was successful - # - # @return [false] - # otherwise - # - # @api private - # - def kill(mutation) - killer = killer(mutation) - reporter.report_killer(killer) - killer.success? - end - - # Return killer for mutation - # - # @return [Killer] - # - # @api private - # - def killer(mutation) - strategy.kill(mutation) - end end end diff --git a/lib/mutant/runner/subject.rb b/lib/mutant/runner/subject.rb new file mode 100644 index 00000000..2b0debc6 --- /dev/null +++ b/lib/mutant/runner/subject.rb @@ -0,0 +1,41 @@ +module Mutant + class Runner + # Subject specific runner + class Subject < self + + # Return mutation runners + # + # @return [Enumerable] + # + # @api private + # + attr_reader :mutations + + # Initialize object + # + # @param [Configuration] config + # @param [Subject] subject + # + # @return [undefined] + # + # @api private + # + def initialize(config, subject) + @subject = subject + super(config) + end + + private + + # Perform operation + # + # @return [undefined] + # + # @api private + # + def run + end + + end + end +end diff --git a/spec/unit/mutant/runner/subject/class_methods/new_spec.rb b/spec/unit/mutant/runner/subject/class_methods/new_spec.rb new file mode 100644 index 00000000..437581bb --- /dev/null +++ b/spec/unit/mutant/runner/subject/class_methods/new_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Mutant::Runner::Subject do + let(:object) { described_class } + + let(:config) { mock('Config') } + let(:mutation) { mock('Mutation') } + let(:mutation_subject) { [mutation] } + + class DummyRunner + include Equalizer.new(:config, :mutation) + + attr_reader :mutation + + def initialize(config, mutation) + @config, @mutation = config, mutation + end + end + + before do + stub_const('Mutant::Runner::Mutation', DummyRunner) + end + + subject { object.new(config, mutation_subject) } + + its(:mutations) { eql([DummyRunner.new(config, mutation)]) } +end From 277a29aba55e359b271c7943a04bbd79d3eaba41 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Fri, 1 Feb 2013 23:39:00 +0100 Subject: [PATCH 46/51] Cleanup killers --- lib/mutant/killer.rb | 109 +++++++++++------------------------- lib/mutant/killer/forked.rb | 10 ---- lib/mutant/killer/rspec.rb | 6 +- lib/mutant/killer/static.rb | 2 - 4 files changed, 36 insertions(+), 91 deletions(-) diff --git a/lib/mutant/killer.rb b/lib/mutant/killer.rb index 165af69b..b7da07b1 100644 --- a/lib/mutant/killer.rb +++ b/lib/mutant/killer.rb @@ -2,11 +2,41 @@ module Mutant # Abstract base class for mutant killers class Killer include Adamantium::Flat, AbstractType, Equalizer.new(:strategy, :mutation, :killed?) + + # Return strategy + # + # @return [Strategy] + # + # @api private + # + attr_reader :strategy + + # Return mutation to kill + # + # @return [Mutation] + # + # @api private + # + attr_reader :mutation + + # Initialize killer object + # + # @param [Strategy] strategy + # @param [Mutation] mutation + # + # @return [undefined] + # + # @api private + # + def initialize(strategy, mutation) + @strategy, @mutation = strategy, mutation + run_with_benchmark + end # Test for kill failure # # @return [true] - # when mutant was killed + # when killer succeeded # # @return [false] # otherwise @@ -40,16 +70,6 @@ module Mutant # attr_reader :runtime - # Return configuration - # - # @return [Configuration] - # - # @api private - # - def configuration - strategy.configuration - end - # Return mutated source # # @return [String] @@ -60,69 +80,8 @@ module Mutant mutation.source end - # Return name of killer - # - # @return [String] - # - # @api private - # - def self.type - self::TYPE - end - - # Return strategy - # - # @return [Strategy] - # - # @api private - # - attr_reader :strategy - - # Return identification - # - # @return [String] - # - # @api private - # - def identification - "#{type}:#{mutation.identification}".freeze - end - memoize :identification - - # Return mae of killer - # - # @return [String] - # - # @api private - # - def type - self.class.type - end - - # Return mutation to kill - # - # @return [Mutation] - # - # @api private - # - attr_reader :mutation - private - # Initialize killer object - # - # @param [Mutation] mutation - # - # @return [undefined] - # - # @api private - # - def initialize(strategy, mutation) - @strategy, @mutation = strategy, mutation - - run_with_benchmark - end - # Run with taking the time # # @return [undefined] @@ -136,13 +95,13 @@ module Mutant @runtime = end_time - start_time end - # Run test + # Run killer # # @return [true] - # returns true when mutant was killed + # when mutant was killed # # @return [false] - # returns false otherwise + # otherwise # # @api private # diff --git a/lib/mutant/killer/forked.rb b/lib/mutant/killer/forked.rb index 3ba5fe49..5056e1fb 100644 --- a/lib/mutant/killer/forked.rb +++ b/lib/mutant/killer/forked.rb @@ -17,16 +17,6 @@ module Mutant super(strategy, mutation) end - # Return killer type - # - # @return [String] - # - # @api private - # - def type - @killer.type - end - private # Run killer diff --git a/lib/mutant/killer/rspec.rb b/lib/mutant/killer/rspec.rb index 6ff447f9..91c40495 100644 --- a/lib/mutant/killer/rspec.rb +++ b/lib/mutant/killer/rspec.rb @@ -3,14 +3,12 @@ module Mutant # Runner for rspec tests class Rspec < self - TYPE = 'rspec'.freeze - private # Run rspec test # # @return [true] - # when test is NOT successful and the mutant was killed + # when test is NOT successful # # @return [false] # otherwise @@ -21,7 +19,6 @@ module Mutant mutation.insert !!::RSpec::Core::Runner.run(command_line_arguments, strategy.error_stream, strategy.output_stream).nonzero? end - memoize :run # Return command line arguments # @@ -34,6 +31,7 @@ module Mutant --fail-fast ) + strategy.spec_files(mutation.subject) end + end end end diff --git a/lib/mutant/killer/static.rb b/lib/mutant/killer/static.rb index 9f64a751..23bc60fb 100644 --- a/lib/mutant/killer/static.rb +++ b/lib/mutant/killer/static.rb @@ -19,13 +19,11 @@ module Mutant # Killer that is always successful class Success < self - TYPE = 'success'.freeze RESULT = true end # Killer that always fails class Fail < self - TYPE = 'fail'.freeze RESULT = false end end From 026e1722ff42a0ab4f5ee7434aa78d0338b82562 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 2 Feb 2013 00:57:09 +0100 Subject: [PATCH 47/51] Use composition library --- Gemfile | 2 ++ lib/mutant.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index 9e11c44a..47c8cb16 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,7 @@ source 'https://rubygems.org' gemspec +gem 'composition', :git => 'https://github.com/mbj/composition.git' + gem 'devtools', :git => 'https://github.com/datamapper/devtools.git' eval(File.read(File.join(File.dirname(__FILE__),'Gemfile.devtools'))) diff --git a/lib/mutant.rb b/lib/mutant.rb index 952a9bc3..c16cb16d 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -10,6 +10,7 @@ require 'digest/sha1' require 'inflecto' require 'to_source' require 'ice_nine' +require 'composition' require 'diff/lcs' require 'diff/lcs/hunk' require 'rspec' From 732ece42c39baf167fc223c0da3e609219af98bf Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 2 Feb 2013 01:00:05 +0100 Subject: [PATCH 48/51] Spec Runner::Subject#mutations in dm2 style --- lib/mutant/runner/subject.rb | 3 +++ .../runner/subject/class_methods/new_spec.rb | 27 ------------------- .../mutant/runner/subject/mutations_spec.rb | 21 +++++++++++++++ 3 files changed, 24 insertions(+), 27 deletions(-) delete mode 100644 spec/unit/mutant/runner/subject/class_methods/new_spec.rb create mode 100644 spec/unit/mutant/runner/subject/mutations_spec.rb diff --git a/lib/mutant/runner/subject.rb b/lib/mutant/runner/subject.rb index 2b0debc6..b8a8d8bf 100644 --- a/lib/mutant/runner/subject.rb +++ b/lib/mutant/runner/subject.rb @@ -34,6 +34,9 @@ module Mutant # @api private # def run + @mutations = @subject.map do |mutation| + Mutation.new(config, mutation) + end end end diff --git a/spec/unit/mutant/runner/subject/class_methods/new_spec.rb b/spec/unit/mutant/runner/subject/class_methods/new_spec.rb deleted file mode 100644 index 437581bb..00000000 --- a/spec/unit/mutant/runner/subject/class_methods/new_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'spec_helper' - -describe Mutant::Runner::Subject do - let(:object) { described_class } - - let(:config) { mock('Config') } - let(:mutation) { mock('Mutation') } - let(:mutation_subject) { [mutation] } - - class DummyRunner - include Equalizer.new(:config, :mutation) - - attr_reader :mutation - - def initialize(config, mutation) - @config, @mutation = config, mutation - end - end - - before do - stub_const('Mutant::Runner::Mutation', DummyRunner) - end - - subject { object.new(config, mutation_subject) } - - its(:mutations) { eql([DummyRunner.new(config, mutation)]) } -end diff --git a/spec/unit/mutant/runner/subject/mutations_spec.rb b/spec/unit/mutant/runner/subject/mutations_spec.rb new file mode 100644 index 00000000..3b9579af --- /dev/null +++ b/spec/unit/mutant/runner/subject/mutations_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Mutant::Runner::Subject, '#mutations' do + let(:object) { described_class.new(config, mutation_subject) } + + subject { object.mutations } + + let(:config) { mock('Config') } + let(:mutation) { mock('Mutation') } + let(:mutation_subject) { [mutation] } + + class DummyRunner + include Composition.new(:config, :mutation) + end + + before do + stub_const('Mutant::Runner::Mutation', DummyRunner) + end + + it { should eql([DummyRunner.new(config, mutation)]) } +end From 2b01374d3ab15f13ac68f8979658f03defa4b854 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 2 Feb 2013 16:32:13 +0100 Subject: [PATCH 49/51] Add runner objects for config and mutation --- lib/mutant.rb | 1 + lib/mutant/cli.rb | 4 +- lib/mutant/runner.rb | 1 + lib/mutant/runner/config.rb | 23 ++++++++++ lib/mutant/runner/mutation.rb | 42 +++++++++++++++++++ lib/mutant/support/method_object.rb | 2 + .../unit/mutant/cli/class_methods/run_spec.rb | 6 +-- .../mutant/runner/config/subjects_spec.rb | 24 +++++++++++ .../mutant/runner/mutation/killer_spec.rb | 31 ++++++++++++++ .../mutant/runner/subject/mutations_spec.rb | 7 +++- 10 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 lib/mutant/runner/config.rb create mode 100644 lib/mutant/runner/mutation.rb create mode 100644 spec/unit/mutant/runner/config/subjects_spec.rb create mode 100644 spec/unit/mutant/runner/mutation/killer_spec.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index c16cb16d..80afe777 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -108,6 +108,7 @@ require 'mutant/strategy/rspec/dm2' require 'mutant/strategy/rspec/dm2/lookup' require 'mutant/strategy/rspec/dm2/lookup/method' require 'mutant/runner' +require 'mutant/runner/config' require 'mutant/runner/subject' require 'mutant/runner/mutation' require 'mutant/cli' diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index 696120aa..484ec836 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -32,7 +32,9 @@ module Mutant # @api private # def self.run(*arguments) - Runner.run(new(*arguments)).success? ? EXIT_SUCCESS : EXIT_FAILURE + config = new(*arguments) + runner = Runner::Config.run(config) + runner.success? ? EXIT_SUCCESS : EXIT_FAILURE rescue Error => exception $stderr.puts(exception.message) EXIT_FAILURE diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index 4359d238..5f20b7d0 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -2,6 +2,7 @@ module Mutant # Runner that allows to mutate an entire project class Runner include Adamantium::Flat, AbstractType + extend MethodObject # Return config # diff --git a/lib/mutant/runner/config.rb b/lib/mutant/runner/config.rb new file mode 100644 index 00000000..296f3b47 --- /dev/null +++ b/lib/mutant/runner/config.rb @@ -0,0 +1,23 @@ +module Mutant + class Runner + class Config < self + + # Return subject runners + # + # @return [Enumerable] + # + # @api private + # + attr_reader :subjects + + private + + def run + @subjects = config.subjects.map do |subject| + Subject.run(config, subject) + end + end + + end + end +end diff --git a/lib/mutant/runner/mutation.rb b/lib/mutant/runner/mutation.rb new file mode 100644 index 00000000..fa56aeae --- /dev/null +++ b/lib/mutant/runner/mutation.rb @@ -0,0 +1,42 @@ +module Mutant + class Runner + # Mutation runner + class Mutation < self + + # Return killer instance + # + # @return [Killer] + # + # @api private + # + attr_reader :killer + + # Initialize object + # + # @param [Configuration] config + # @param [Mutation] mutation + # + # @return [undefined] + # + # @api private + # + def initialize(config, mutation) + @mutation = mutation + super(config) + end + + private + + # Perform operation + # + # @return [undefined] + # + # @api private + # + def run + @killer = config.strategy(@mutation).kill(@mutation) + end + + end + end +end diff --git a/lib/mutant/support/method_object.rb b/lib/mutant/support/method_object.rb index 87c7d809..72c9cb4c 100644 --- a/lib/mutant/support/method_object.rb +++ b/lib/mutant/support/method_object.rb @@ -1,6 +1,7 @@ module Mutant # A mixing to create method object semantics module MethodObject + # Hook called when descendant is extended # # @param [Module|Class] descendant @@ -27,5 +28,6 @@ module Mutant def run(*args) new(*args) end + end end diff --git a/spec/unit/mutant/cli/class_methods/run_spec.rb b/spec/unit/mutant/cli/class_methods/run_spec.rb index 2528611a..21fb974b 100644 --- a/spec/unit/mutant/cli/class_methods/run_spec.rb +++ b/spec/unit/mutant/cli/class_methods/run_spec.rb @@ -11,7 +11,7 @@ describe Mutant::CLI, '.run' do before do described_class.stub(:new => instance) - Mutant::Runner.stub(:run => runner) + Mutant::Runner::Config.stub(:run => runner) end context 'when runner is successful' do @@ -20,7 +20,7 @@ describe Mutant::CLI, '.run' do it { should be(0) } it 'should run with attributes' do - Mutant::Runner.should_receive(:run).with(instance).and_return(runner) + Mutant::Runner::Config.should_receive(:run).with(instance).and_return(runner) should be(0) end end @@ -31,7 +31,7 @@ describe Mutant::CLI, '.run' do it { should be(1) } it 'should run with attributes' do - Mutant::Runner.should_receive(:run).with(instance).and_return(runner) + Mutant::Runner::Config.should_receive(:run).with(instance).and_return(runner) should be(1) end end diff --git a/spec/unit/mutant/runner/config/subjects_spec.rb b/spec/unit/mutant/runner/config/subjects_spec.rb new file mode 100644 index 00000000..be0e1c32 --- /dev/null +++ b/spec/unit/mutant/runner/config/subjects_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Mutant::Runner::Config, '#subjects' do + let(:object) { described_class.run(config) } + + subject { object.subjects } + + let(:config) { mock('Config', :subjects => [mutation_subject]) } + let(:mutation_subject) { mock('Mutation subject') } + let(:subject_runner) { mock('Subject runner') } + + class DummySubjectRunner + include Composition.new(:config, :mutation) + def self.run(*args); new(*args); end + end + + before do + stub_const('Mutant::Runner::Subject', DummySubjectRunner) + end + + it { should eql([DummySubjectRunner.new(config, mutation_subject)]) } + + it_should_behave_like 'an idempotent method' +end diff --git a/spec/unit/mutant/runner/mutation/killer_spec.rb b/spec/unit/mutant/runner/mutation/killer_spec.rb new file mode 100644 index 00000000..74e161ab --- /dev/null +++ b/spec/unit/mutant/runner/mutation/killer_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Mutant::Runner::Mutation, '#killer' do + let(:object) { described_class.run(config, mutation) } + + let(:config) { mock('Config') } + let(:mutation) { mock('Mutation') } + let(:strategy) { mock('Strategy') } + let(:killer) { mock('Killer') } + + subject { object.killer } + + before do + config.stub(:strategy => strategy) + strategy.stub(:kill => killer) + end + + it 'should call configuration to identify strategy' do + config.should_receive(:strategy).with(mutation).and_return(strategy) + should be(killer) + end + + it 'should run killer' do + strategy.should_receive(:kill).with(mutation).and_return(killer) + should be(killer) + end + + it { should be(killer) } + + it_should_behave_like 'an idempotent method' +end diff --git a/spec/unit/mutant/runner/subject/mutations_spec.rb b/spec/unit/mutant/runner/subject/mutations_spec.rb index 3b9579af..99c93fa4 100644 --- a/spec/unit/mutant/runner/subject/mutations_spec.rb +++ b/spec/unit/mutant/runner/subject/mutations_spec.rb @@ -1,16 +1,17 @@ require 'spec_helper' describe Mutant::Runner::Subject, '#mutations' do - let(:object) { described_class.new(config, mutation_subject) } + let(:object) { described_class.run(config, mutation_subject) } subject { object.mutations } - let(:config) { mock('Config') } + let(:config) { mock('Config') } let(:mutation) { mock('Mutation') } let(:mutation_subject) { [mutation] } class DummyRunner include Composition.new(:config, :mutation) + def self.run(*args); new(*args); end end before do @@ -18,4 +19,6 @@ describe Mutant::Runner::Subject, '#mutations' do end it { should eql([DummyRunner.new(config, mutation)]) } + + it_should_behave_like 'an idempotent method' end From 71c4a1999702428a38b2b9b904218e0168366444 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 2 Feb 2013 16:56:48 +0100 Subject: [PATCH 50/51] Move CLI parsing specific stuff in subclass Not ideal, but a first step to improve SRP here. --- lib/mutant.rb | 1 + lib/mutant/cli.rb | 122 ++++-------------- lib/mutant/cli_parser.rb | 87 +++++++++++++ .../unit/mutant/cli/class_methods/new_spec.rb | 2 +- 4 files changed, 117 insertions(+), 95 deletions(-) create mode 100644 lib/mutant/cli_parser.rb diff --git a/lib/mutant.rb b/lib/mutant.rb index 80afe777..7d376f5e 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -111,6 +111,7 @@ require 'mutant/runner' require 'mutant/runner/config' require 'mutant/runner/subject' require 'mutant/runner/mutation' +require 'mutant/cli_parser' require 'mutant/cli' require 'mutant/cli/classifier' require 'mutant/cli/classifier/namespace' diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index 484ec836..9ca5755c 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -1,27 +1,9 @@ module Mutant + # Comandline parser - class CLI + class CLI < CLIParser include Adamantium::Flat, Equalizer.new(:matcher, :filter, :strategy, :reporter) - # Error raised when CLI argv is inalid - Error = Class.new(RuntimeError) - - EXIT_FAILURE = 1 - EXIT_SUCCESS = 0 - - OPTIONS = { - '--code' => [:add_filter, Mutation::Filter::Code ], - '--debug' => [:set_debug ], - '-d' => [:set_debug ], - '--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ], - '--rspec-full' => [:set_strategy, Strategy::Rspec::Full ], - '--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ], - '--static-fail' => [:set_strategy, Strategy::Static::Fail ], - '--static-success' => [:set_strategy, Strategy::Static::Success ] - }.freeze - - OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze - # Run cli with arguments # # @param [Array] arguments @@ -40,23 +22,29 @@ module Mutant EXIT_FAILURE end - # Return matcher + OPTIONS = { + '--code' => [:add_filter, Mutation::Filter::Code ], + '--debug' => [:set_debug ], + '-d' => [:set_debug ], + '--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ], + '--rspec-full' => [:set_strategy, Strategy::Rspec::Full ], + '--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ] + }.freeze + + # Initialize objecct # - # @return [Mutant::Matcher] + # @param [Array] # - # @raise [CLI::Error] - # raises error when matcher is not given + # @return [undefined] # # @api private # - def matcher - if @matchers.empty? - raise Error, 'No matchers given' - end - - Mutant::Matcher::Chain.build(@matchers) + def initialize(arguments) + @filters, @matchers = [], [] + super(arguments) + strategy + matcher end - memoize :matcher # Test for running in debug mode # @@ -112,77 +100,23 @@ module Mutant private - # Initialize CLI + # Return matcher # - # @param [Array] arguments - # - # @return [undefined] - # - # @api private - # - def initialize(arguments) - @filters, @matchers = [], [] - - @arguments, @index = arguments, 0 - - while @index < @arguments.length - dispatch - end - - strategy - matcher - end - - # Return option for argument with index - # - # @param [Fixnum] index - # - # @return [String] - # - # @api private - # - def option(index) - @arguments.fetch(index+1) - end - - # Return current argument - # - # @return [String] - # - # @api private - # - def current_argument - @arguments.fetch(@index) - end - - # Return current option value - # - # @return [String] + # @return [Mutant::Matcher] # # @raise [CLI::Error] - # raises error when option is missing + # raises error when matcher is not given # # @api private # - def current_option_value - @arguments.fetch(@index+1) - rescue IndexError - raise Error, "#{current_argument.inspect} is missing an argument" - end - - # Process current argument - # - # @return [undefined] - # - # @api private - # - def dispatch - if OPTION_PATTERN =~ current_argument - dispatch_option - else - dispatch_matcher + def matcher + if @matchers.empty? + raise Error, 'No matchers given' end + + Mutant::Matcher::Chain.build(@matchers) end + memoize :matcher # Move processed argument by amount # diff --git a/lib/mutant/cli_parser.rb b/lib/mutant/cli_parser.rb new file mode 100644 index 00000000..3e3c7957 --- /dev/null +++ b/lib/mutant/cli_parser.rb @@ -0,0 +1,87 @@ +module Mutant + # Base class for cli parsers + # + # I hate base classes for reusable functionallity. + # But could not come up with a nice composition/instantiation + # solution. + # + class CLIParser + + # Error raised when CLI argv is inalid + Error = Class.new(RuntimeError) + + EXIT_FAILURE = 1 + EXIT_SUCCESS = 0 + + OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze + + # Initialize CLI + # + # @param [Array] arguments + # + # @return [undefined] + # + # @api private + # + def initialize(arguments) + @arguments, @index = arguments, 0 + while @index < @arguments.length + dispatch + end + end + + private + + # Return option for argument with index + # + # @param [Fixnum] index + # + # @return [String] + # + # @api private + # + def option(index) + @arguments.fetch(index+1) + end + + # Return current argument + # + # @return [String] + # + # @api private + # + def current_argument + @arguments.fetch(@index) + end + + # Return current option value + # + # @return [String] + # + # @raise [CLI::Error] + # raises error when option is missing + # + # @api private + # + def current_option_value + @arguments.fetch(@index+1) + rescue IndexError + raise Error, "#{current_argument.inspect} is missing an argument" + end + + # Process current argument + # + # @return [undefined] + # + # @api private + # + def dispatch + if OPTION_PATTERN =~ current_argument + dispatch_option + else + dispatch_matcher + end + 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 79d1fc4e..82b8942b 100644 --- a/spec/unit/mutant/cli/class_methods/new_spec.rb +++ b/spec/unit/mutant/cli/class_methods/new_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' shared_examples_for 'an invalid cli run' do it 'should raise error' do - expect { subject }.to raise_error(described_class::Error, expected_message) + expect { subject }.to raise_error(Mutant::CLIParser::Error, expected_message) end end From 6c2bed10f418476f1778cc433419dc7edce9bff3 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Fri, 8 Feb 2013 01:40:19 +0100 Subject: [PATCH 51/51] Use reflective codeclimate badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6965e8d..b7908c46 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ mutant [![Build Status](https://secure.travis-ci.org/mbj/mutant.png?branch=master)](http://travis-ci.org/mbj/mutant) [![Dependency Status](https://gemnasium.com/mbj/mutant.png)](https://gemnasium.com/mbj/mutant) -[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/mbj/mutant) +[![Code Climate](https://codeclimate.com/github/mbj/mutant.png)](https://codeclimate.com/github/mbj/mutant) Mutant is a mutation testing tool for ruby that aims to be better than existing mutation testers.