diff --git a/Gemfile b/Gemfile index 43bb92cc..0a72c226 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,8 @@ gemspec gem 'mutant', path: '.' +gem 'rspec-core', path: '../rspec-core' + group :development, :test do gem 'devtools', git: 'https://github.com/rom-rb/devtools.git' end diff --git a/lib/mutant.rb b/lib/mutant.rb index 311dd75b..a2116e5c 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -19,6 +19,7 @@ require 'diff/lcs/hunk' require 'rspec' require 'anima' require 'concord' +require 'rspec' # Library namespace module Mutant diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index 34b19fc6..9feb1095 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -244,6 +244,9 @@ module Mutant # @api private # def add_strategies(opts) + opts.separator '' + opts.separator 'Strategies:' + opts.on('--static-success', 'does succeed on all mutations') do set_strategy Strategy::Static::Success.new end diff --git a/lib/mutant/context/scope.rb b/lib/mutant/context/scope.rb index 6d5c01a2..0ad28624 100644 --- a/lib/mutant/context/scope.rb +++ b/lib/mutant/context/scope.rb @@ -89,6 +89,17 @@ module Mutant scope.name end + # Return match prefixes + # + # @return [Enumerable] + # + # @api private + # + def match_prefixes + name_nesting.reverse + end + memoize :match_prefixes + # Return scope wrapped by context # # @return [::Module|::Class] diff --git a/lib/mutant/killer.rb b/lib/mutant/killer.rb index c6b61fde..469b9fa4 100644 --- a/lib/mutant/killer.rb +++ b/lib/mutant/killer.rb @@ -98,6 +98,16 @@ module Mutant @runtime = times.real end + # Return subject + # + # @return [Subject] + # + # @api private + # + def subject + mutation.subject + end + # Run killer # # @return [true] diff --git a/lib/mutant/killer/rspec.rb b/lib/mutant/killer/rspec.rb index 845c3d0a..825d630f 100644 --- a/lib/mutant/killer/rspec.rb +++ b/lib/mutant/killer/rspec.rb @@ -19,36 +19,72 @@ module Mutant # def run mutation.insert - # TODO: replace with real streams from configuration - require 'stringio' - # Note: We assume interesting output from a failed rspec run is stderr. - rspec_err = StringIO.new - exit_code = ::RSpec::Core::Runner.run(cli_arguments, nil, rspec_err) + groups = example_groups - killed = !exit_code.zero? - - if killed and mutation.should_survive? - rspec_err.rewind - - puts "#{mutation.class} test failed." - puts 'RSpec stderr:' - puts rspec_err.read + unless groups + $stderr.puts "No rspec example groups found for: #{match_prefixes.join(', ')}" + return false end - killed + strategy.configuration.reporter.report(groups.length, nil) do |reporter| + example_groups.each do |group| + return true unless group.run(reporter) + end.all? + + return false + end end - # Return command line arguments + # Return match prefixes # - # @return [Array] + # @return [Enumerble] # # @api private # - def cli_arguments - %W( - --fail-fast - ) + strategy.spec_files(mutation.subject) + def match_prefixes + subject.match_prefixes + end + + # Return example groups + # + # @return [Array] + # + # @api private + # + def example_groups + match_prefixes.each do |match_expression| + example_groups = find_with(match_expression) + return example_groups unless example_groups.empty? + end + + nil + end + + # Return example groups that match expression + # + # @param [String] match_expression + # + # @return [Enumerable] + # + # @api private + # + def find_with(match_expression) + all_example_groups.select do |example_group| + example_group.description.start_with?(match_expression) + end + end + + DELIMITERS = /::|#/.freeze + + # Return all example groups + # + # @return [Enumerable] + # + # @api private + # + def all_example_groups + strategy.example_groups end end # Rspec diff --git a/lib/mutant/strategy/rspec.rb b/lib/mutant/strategy/rspec.rb index 828cca11..7376122f 100644 --- a/lib/mutant/strategy/rspec.rb +++ b/lib/mutant/strategy/rspec.rb @@ -15,8 +15,61 @@ module Mutant # @api private # def setup + output = StringIO.new + configuration.error_stream = output + configuration.output_stream = output + options.configure(configuration) + configuration.load_spec_files self end + memoize :setup + + # Return configuration + # + # @return [RSpec::Core::Configuration] + # + # @api private + # + def configuration + RSpec::Core::Configuration.new + end + memoize :configuration, :freezer => :noop + + # Return example groups + # + # @return [Enumerable] + # + # @api private + # + def example_groups + world.example_groups + end + + private + + # Return world + # + # @return [RSpec::Core::World] + # + # @api private + # + def world + RSpec.world + end + memoize :world, :freezer => :noop + + # Return options + # + # @return [RSpec::Core::ConfigurationOptions] + # + # @api private + # + def options + options = RSpec::Core::ConfigurationOptions.new(%w(--fail-fast spec)) + options.parse_options + options + end + memoize :options, :freezer => :noop end # Rspec end # Strategy diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index c6013921..19c62f6e 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -46,7 +46,7 @@ module Mutant # @api private # def identification - "#{subtype}:#{source_path}:#{source_line}" + "#{match_expression}:#{source_path}:#{source_line}" end memoize :identification @@ -84,16 +84,26 @@ module Mutant end memoize :original_root - private - - # Return subtype identifier + # Return match expression # # @return [String] # # @api private # - abstract_method :subtype - private :subtype + abstract_method :match_expression + + # Return match prefixes + # + # @return [Enumerable] + # + # @api private + # + def match_prefixes + context.match_prefixes.dup << match_expression + end + memoize :match_prefixes + + private # Return neutral mutation # diff --git a/lib/mutant/subject/method.rb b/lib/mutant/subject/method.rb index 2a73efd3..cafebcba 100644 --- a/lib/mutant/subject/method.rb +++ b/lib/mutant/subject/method.rb @@ -27,6 +27,16 @@ module Mutant node.children[self.class::NAME_INDEX] end + # Return match expression + # + # @return [String] + # + # @api private + # + def match_expression + "#{context.identification}#{self.class::SYMBOL}#{name}" + end + private # Return mutations @@ -54,16 +64,6 @@ module Mutant context.scope end - # Return subtype identifier - # - # @return [String] - # - # @api private - # - def subtype - "#{context.identification}#{self.class::SYMBOL}#{name}" - end - end # Method end # Subject end # Mutant diff --git a/spec/integration/mutant/rspec_killer_spec.rb b/spec/integration/mutant/rspec_spec.rb similarity index 73% rename from spec/integration/mutant/rspec_killer_spec.rb rename to spec/integration/mutant/rspec_spec.rb index 8922f9c9..e8115423 100644 --- a/spec/integration/mutant/rspec_killer_spec.rb +++ b/spec/integration/mutant/rspec_spec.rb @@ -10,11 +10,8 @@ describe Mutant, 'rspec integration' do end end - let(:strategy) { Mutant::Strategy::Rspec::DM2 } - - pending 'allows to kill mutations' do - cli = 'bundle exec mutant --rspec ::TestApp::Literal#string' - Kernel.system(cli).should be(true) + specify 'it allows to kill mutations' do + Kernel.system('bundle exec mutant --rspec ::TestApp::Literal#string').should be(true) end pending 'fails to kill mutations when they are not covered' do 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 5081b326..c15f0e6a 100644 --- a/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +++ b/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb @@ -4,6 +4,10 @@ require 'spec_helper' describe Mutant::Killer::Rspec, '.new' do + before do + pending 'dactivated' + end + subject { object.new(strategy, mutation) } let(:context) { double('Context') }