Initial rspec killer with "longes description prefix match"
This commit is contained in:
		
							parent
							
								
									ad0ea265a2
								
							
						
					
					
						commit
						f70dc6703f
					
				
					 11 changed files with 168 additions and 41 deletions
				
			
		
							
								
								
									
										2
									
								
								Gemfile
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ require 'diff/lcs/hunk'
 | 
			
		|||
require 'rspec'
 | 
			
		||||
require 'anima'
 | 
			
		||||
require 'concord'
 | 
			
		||||
require 'rspec'
 | 
			
		||||
 | 
			
		||||
# Library namespace
 | 
			
		||||
module Mutant
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,6 +89,17 @@ module Mutant
 | 
			
		|||
        scope.name
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Return match prefixes
 | 
			
		||||
      #
 | 
			
		||||
      # @return [Enumerable<String>]
 | 
			
		||||
      #
 | 
			
		||||
      # @api private
 | 
			
		||||
      #
 | 
			
		||||
      def match_prefixes
 | 
			
		||||
        name_nesting.reverse
 | 
			
		||||
      end
 | 
			
		||||
      memoize :match_prefixes
 | 
			
		||||
 | 
			
		||||
      # Return scope wrapped by context
 | 
			
		||||
      #
 | 
			
		||||
      # @return [::Module|::Class]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String>]
 | 
			
		||||
      #
 | 
			
		||||
      # @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<RSpec::Example>]
 | 
			
		||||
      #
 | 
			
		||||
      # @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<String>]
 | 
			
		||||
      #
 | 
			
		||||
      # @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<RSpec::Example>]
 | 
			
		||||
      #
 | 
			
		||||
      # @api private
 | 
			
		||||
      #
 | 
			
		||||
      def all_example_groups
 | 
			
		||||
        strategy.example_groups
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    end # Rspec
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<RSpec::Core::ExampleGroup>]
 | 
			
		||||
      #
 | 
			
		||||
      # @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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String>]
 | 
			
		||||
    #
 | 
			
		||||
    # @api private
 | 
			
		||||
    #
 | 
			
		||||
    def match_prefixes
 | 
			
		||||
      context.match_prefixes.dup << match_expression
 | 
			
		||||
    end
 | 
			
		||||
    memoize :match_prefixes
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
    # Return neutral mutation
 | 
			
		||||
    #
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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')          }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue