From 40d5230c6843ac621884ea9efd998264cb96e1c0 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Tue, 11 Dec 2012 00:17:19 +0100 Subject: [PATCH] Introduce noop mutation guards and argument mutators * Sorry for not splitting up into smaller commit --- Changelog.md | 6 +- Gemfile | 2 + lib/mutant.rb | 13 ++- lib/mutant/cli.rb | 73 ++++++++-------- lib/mutant/killer/rspec.rb | 2 +- lib/mutant/mutation.rb | 26 ++++++ lib/mutant/mutator.rb | 35 ++++++-- lib/mutant/mutator/node.rb | 41 +++------ lib/mutant/mutator/node/arguments.rb | 80 +++++++++++++---- lib/mutant/mutator/node/iter_19.rb | 3 +- .../mutator/node/local_variable_assignment.rb | 25 ++++++ lib/mutant/mutator/node/noop.rb | 1 + lib/mutant/reporter.rb | 28 ++++++ lib/mutant/reporter/cli.rb | 86 ++++++++++++++++--- lib/mutant/reporter/null.rb | 8 +- lib/mutant/reporter/stats.rb | 34 ++++++-- lib/mutant/runner.rb | 43 +++++++++- lib/mutant/strategy.rb | 49 ++++++++++- lib/mutant/strategy/rspec.rb | 8 +- lib/mutant/subject.rb | 11 +++ spec/shared/mutator_behavior.rb | 14 ++- .../killer/rspec/class_methods/new_spec.rb | 2 +- .../mutator/node/define/mutation_spec.rb | 19 ++++ .../mutant/mutator/node/send/mutation_spec.rb | 2 +- 24 files changed, 478 insertions(+), 133 deletions(-) create mode 100644 lib/mutant/mutator/node/local_variable_assignment.rb diff --git a/Changelog.md b/Changelog.md index 269915c6..5c2b88f8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,10 @@ # v0.2.4 2012-12-08 -* [feature] define block arguments +* [feature] Run noop mutation per subject to guard against initial failing specs +* [feature] Mutate default into required arguments +* [feature] Mutate default literals +* [feature] Mutate unwinding of pattern args |(a, b), c] => |a, b, c| +* [feature] Mutate define and block arguments * [feature] Mutate block arguments, inklusive pattern args * [feature] Recurse into block bodies * [fixed] Crash on mutating yield, added a noop for now diff --git a/Gemfile b/Gemfile index 8a136bfc..b4bc1a69 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,7 @@ source 'https://rubygems.org' gemspec +gem 'to_source', :path => '../to_source' + gem 'devtools', :git => 'https://github.com/mbj/devtools.git' eval(File.read(File.join(File.dirname(__FILE__),'Gemfile.devtools'))) diff --git a/lib/mutant.rb b/lib/mutant.rb index 5bab81e1..d5696efd 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -9,11 +9,21 @@ require 'digest/sha1' require 'to_source' require 'inflector' require 'ice_nine' -require 'ice_nine/core_ext/object' require 'diff/lcs' require 'diff/lcs/hunk' require 'rspec' +module IceNine + class Freezer + class Rubinius + class AST < IceNine::Freezer::Object + class Node < IceNine::Freezer::Object + end + end + end + end +end + # Library namespace module Mutant @@ -74,6 +84,7 @@ require 'mutant/mutator/node/send' require 'mutant/mutator/node/arguments' require 'mutant/mutator/node/define' require 'mutant/mutator/node/return' +require 'mutant/mutator/node/local_variable_assignment' require 'mutant/mutator/node/iter_19' require 'mutant/mutator/node/if_statement' require 'mutant/mutator/node/receiver_case' diff --git a/lib/mutant/cli.rb b/lib/mutant/cli.rb index ee5a54d3..a0ed01a9 100644 --- a/lib/mutant/cli.rb +++ b/lib/mutant/cli.rb @@ -1,7 +1,7 @@ module Mutant # Comandline parser class CLI - include Adamantium::Flat, Equalizer.new(:matcher, :filter, :killer) + include Adamantium::Flat, Equalizer.new(:matcher, :filter, :strategy, :reporter) # Error raised when CLI argv is inalid Error = Class.new(RuntimeError) @@ -44,6 +44,20 @@ module Mutant end memoize :matcher + # Test for running in debug mode + # + # @return [true] + # if debug mode is active + # + # @return [false] + # otherwise + # + # @api private + # + def debug? + !!@debug + end + # Return mutation filter # # @return [Mutant::Matcher] @@ -67,7 +81,9 @@ module Mutant # def strategy @strategy || raise(Error, 'no strategy was set!') + @strategy.new(self) end + memoize :strategy # Return reporter # @@ -76,7 +92,7 @@ module Mutant # @api private # def reporter - Mutant::Reporter::CLI.new($stdout) + Mutant::Reporter::CLI.new(self) end memoize :reporter @@ -88,7 +104,8 @@ module Mutant '--include' => [:add_load_path ], '-r' => [:require_library ], '--require' => [:require_library ], - #'--killer-fork' => [:enable_killer_fork ], + '--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 ], @@ -98,33 +115,6 @@ module Mutant OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze - # Return selected killer - # - # @return [Killer] - # - # @api private - # - def selected_killer - unless @rspec - raise Error, "Only rspec is supported currently use --rspec switch" - end - - Mutant::Killer::Rspec - end - memoize :selected_killer - - # Return option for argument with index - # - # @param [Fixnum] index - # - # @return [String] - # - # @api private - # - def option(index) - @arguments.fetch(index+1) - end - # Initialize CLI # # @param [Array] arguments @@ -146,6 +136,18 @@ module Mutant 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] @@ -268,17 +270,15 @@ module Mutant @rspec = true end - # Enable killer forking + # Set debug mode # # @api private # - # @return [self] + # @return [undefined] # - # @api private - # - def enable_killer_fork + def set_debug consume(1) - @forking = true + @debug = true end # Set strategy @@ -304,6 +304,5 @@ module Mutant require(current_option_value) consume(2) end - end end diff --git a/lib/mutant/killer/rspec.rb b/lib/mutant/killer/rspec.rb index df3b1fe7..2c7ee826 100644 --- a/lib/mutant/killer/rspec.rb +++ b/lib/mutant/killer/rspec.rb @@ -29,7 +29,7 @@ module Mutant # def run mutation.insert - !::RSpec::Core::Runner.run(command_line_arguments, @error_stream, @output_stream).zero? + !::RSpec::Core::Runner.run(command_line_arguments, strategy.error_stream, strategy.output_stream).zero? end memoize :run diff --git a/lib/mutant/mutation.rb b/lib/mutant/mutation.rb index 1f4062b6..c705506f 100644 --- a/lib/mutant/mutation.rb +++ b/lib/mutant/mutation.rb @@ -109,5 +109,31 @@ module Mutant def initialize(subject, node) @subject, @node = subject, node end + + class Noop < self + + # Initialihe 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/mutator.rb b/lib/mutant/mutator.rb index d263aee9..3129a10d 100644 --- a/lib/mutant/mutator.rb +++ b/lib/mutant/mutator.rb @@ -32,6 +32,16 @@ module Mutant end private_class_method :handle + # Return identity of object (for deduplication) + # + # @param [Object] + # + # @return [Object] + # + def self.identity(object) + object + end + # Return input # # @return [Object] @@ -52,12 +62,13 @@ module Mutant # @api private # def initialize(input, block) - @input, @block = Helper.deep_clone(input), block - IceNine.deep_freeze(@input) + @input, @block = IceNine.deep_freeze(input), block + @seen = Set.new + guard(input) dispatch end - # Test if generated object is different from input + # Test if generated object is not guarded from emmitting # # @param [Object] object # @@ -69,7 +80,19 @@ module Mutant # @api private # def new?(object) - input != object + !@seen.include?(self.class.identity(object)) + end + + # Add object to guarded values + # + # @param [Object] object + # + # @return [undefined] + # + # @api private + # + def guard(object) + @seen << self.class.identity(object) end # Test if generated mutation is allowed @@ -105,6 +128,8 @@ module Mutant def emit(object) return unless new?(object) and allow?(object) + guard(object) + emit!(object) end @@ -160,7 +185,7 @@ module Mutant # @api private # def dup_input - input.dup + Helper.deep_clone(input) end end diff --git a/lib/mutant/mutator/node.rb b/lib/mutant/mutator/node.rb index 2239f79d..f0b9c372 100644 --- a/lib/mutant/mutator/node.rb +++ b/lib/mutant/mutator/node.rb @@ -5,36 +5,21 @@ module Mutant class Node < self include AbstractType + # Return identity of node + # + # @param [Rubinius::AST::Node] node + # + # @return [String] + # + def self.identity(node) + ToSource.to_source(node) + end + private alias_method :node, :input alias_method :dup_node, :dup_input - # Return source of input node - # - # @return [String] - # - # @api private - # - def source - ToSource.to_source(node) - end - memoize :source - - # Test if generated node is new - # - # @return [true] - # if generated node is different from input - # - # @return [false] - # otherwise - # - # @api private - # - def new?(node) - source != ToSource.to_source(node) - end - # Emit a new AST node # # @param [Rubinis::AST::Node:Class] node_class @@ -116,8 +101,8 @@ module Mutant Mutator.each(body) do |mutation| dup = dup_node - yield mutation if block_given? dup.public_send(:"#{name}=", mutation) + yield dup if block_given? emit(dup) end end @@ -156,9 +141,7 @@ module Mutant # # @api private # - def dup_node - node.dup - end + alias_method :dup_node, :dup_input end end end diff --git a/lib/mutant/mutator/node/arguments.rb b/lib/mutant/mutator/node/arguments.rb index 87bd099e..b9a3a0b5 100644 --- a/lib/mutant/mutator/node/arguments.rb +++ b/lib/mutant/mutator/node/arguments.rb @@ -17,23 +17,24 @@ module Mutant def dispatch emit_attribute_mutations(:name) end + end - # Test if node is new + # Mutantor for default arguments + class DefaultArguments < self + handle(Rubinius::AST::DefaultArguments) + + private + + # Emit mutations # - # Note: to_source does not handle PatternVariableNodes as entry points - # - # @param [Rubinius::AST::Node] generated - # - # @return [true] - # if node is new - # - # @return [false] - # otherwise + # @return [undefined] # # @api private # - def new?(generated) - node.name != generated.name + def dispatch + emit_attribute_mutations(:arguments) do |argument| + argument.names = argument.arguments.map(&:name) + end end end @@ -53,9 +54,7 @@ module Mutant def dispatch Mutator.each(node.arguments.body) do |mutation| dup = dup_node - dup_args = dup.arguments.dup - dup_args.body = mutation - dup.arguments = dup_args + dup.arguments.body = mutation emit(dup) end end @@ -90,7 +89,51 @@ module Mutant # def dispatch expand_pattern_args - emit_attribute_mutations(:required) + emit_default_mutations + emit_required_defaults_mutation + emit_attribute_mutations(:required) do |mutation| + mutation.names = mutation.optional + mutation.required + end + end + + # Emit default mutations + # + # @return [undefined] + # + # @api private + # + def emit_default_mutations + return unless node.defaults + emit_attribute_mutations(:defaults) do |mutation| + mutation.optional = mutation.defaults.names + mutation.names = mutation.required + mutation.optional + if mutation.defaults.names.empty? + mutation.defaults = nil + end + end + end + + # Emit required defaults mutations + # + # @return [undefined] + # + # @api private + # + def emit_required_defaults_mutation + return unless node.defaults + arguments = node.defaults.arguments + arguments.each_index do |index| + names = arguments.take(index+1).map(&:name) + dup = dup_node + defaults = dup.defaults + defaults.arguments = defaults.arguments.drop(names.size) + names.each { |name| dup.optional.delete(name) } + dup.required.concat(names) + if dup.optional.empty? + dup.defaults = nil + end + emit(dup) + end end # Emit pattern args expansions @@ -102,13 +145,12 @@ module Mutant def expand_pattern_args node.required.each_with_index do |argument, index| next unless argument.kind_of?(Rubinius::AST::PatternArguments) - required = node.required.dup + dup = dup_node + required = dup.required required.delete_at(index) argument.arguments.body.reverse.each do |node| required.insert(index, node.name) end - dup = dup_node - dup.required = required dup.names |= required emit(dup) end diff --git a/lib/mutant/mutator/node/iter_19.rb b/lib/mutant/mutator/node/iter_19.rb index be094253..44a1128d 100644 --- a/lib/mutant/mutator/node/iter_19.rb +++ b/lib/mutant/mutator/node/iter_19.rb @@ -15,7 +15,8 @@ module Mutant def dispatch emit_attribute_mutations(:body) emit_attribute_mutations(:arguments) do |mutation| - mutation.names = mutation.required + arguments = mutation.arguments + arguments.names = arguments.required + arguments.optional end if node.arguments end diff --git a/lib/mutant/mutator/node/local_variable_assignment.rb b/lib/mutant/mutator/node/local_variable_assignment.rb new file mode 100644 index 00000000..43570ce8 --- /dev/null +++ b/lib/mutant/mutator/node/local_variable_assignment.rb @@ -0,0 +1,25 @@ +module Mutant + class Mutator + class Node + class LocalVariableAssignment < self + + handle(Rubinius::AST::LocalVariableAssignment) + + private + + # Emit mutants + # + # @return [undefined] + # + # @api private + # + def dispatch + emit_attribute_mutations(:name) + emit_attribute_mutations(:value) + end + + end + end + end +end + diff --git a/lib/mutant/mutator/node/noop.rb b/lib/mutant/mutator/node/noop.rb index f3ed12b6..ac6f03b4 100644 --- a/lib/mutant/mutator/node/noop.rb +++ b/lib/mutant/mutator/node/noop.rb @@ -34,6 +34,7 @@ module Mutant handle(Rubinius::AST::File) handle(Rubinius::AST::DynamicRegex) handle(Rubinius::AST::OpAssignOr19) + handle(Rubinius::AST::BlockPass19) handle(Rubinius::AST::OpAssign1) handle(Rubinius::AST::Or) handle(Rubinius::AST::ConstantAccess) diff --git a/lib/mutant/reporter.rb b/lib/mutant/reporter.rb index 1d54073a..469efc11 100644 --- a/lib/mutant/reporter.rb +++ b/lib/mutant/reporter.rb @@ -52,5 +52,33 @@ module Mutant # @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 + end end end diff --git a/lib/mutant/reporter/cli.rb b/lib/mutant/reporter/cli.rb index e8162098..934a566a 100644 --- a/lib/mutant/reporter/cli.rb +++ b/lib/mutant/reporter/cli.rb @@ -17,6 +17,26 @@ module Mutant puts("Subject: #{subject.identification}") end + # Return error stream + # + # @return [IO] + # + # @api private + # + def error_stream + @config.debug? ? io : StringIO.new + end + + # Return output stream + # + # @return [IO] + # + # @api private + # + def output_stream + @config.debug? ? io : StringIO.new + end + # Report mutation # # @param [Mutation] mutation @@ -26,7 +46,10 @@ module Mutant # @api private # def mutation(mutation) - #colorized_diff(mutation.original_source, mutation.source) + if @config.debug? + colorized_diff(mutation) + end + self end @@ -45,6 +68,32 @@ module Mutant puts "Strategy: #{config.strategy.inspect}" 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 + + puts(colorize(color, "#{word}: #{killer.identification} (%02.2fs)" % killer.runtime)) + + unless killer.fail? + puts(killer.mutation.source) + stats.noop_fail(killer) + end + + self + end + # Reporter killer # # @param [Killer] killer @@ -66,8 +115,7 @@ module Mutant puts(colorize(color, "#{word}: #{killer.identification} (%02.2fs)" % killer.runtime)) if killer.fail? - mutation = killer.mutation - colorized_diff(mutation.original_source, mutation.source) + colorized_diff(killer.mutation) end self @@ -87,12 +135,13 @@ module Mutant end puts - puts "subjects: #{stats.subject}" - puts "mutations: #{stats.mutation}" - puts "kills: #{stats.kill}" - puts "alive: #{stats.alive}" - puts "mtime: %02.2fs" % stats.time - puts "rtime: %02.2fs" % stats.runtime + 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 @@ -121,8 +170,9 @@ module Mutant # # @api private # - def initialize(io) - @io = io + def initialize(config) + super + @io = $stdout @stats = Stats.new end @@ -136,7 +186,7 @@ module Mutant # def failure(killer) puts(colorize(Color::RED, "!!! Mutant alive: #{killer.identification} !!!")) - colorized_diff(killer.original_source, killer.mutation_source) + colorized_diff(killer.mutation) puts("Took: (%02.2fs)" % killer.runtime) end @@ -187,17 +237,25 @@ module Mutant # @param [String] original # @param [String] current # - # @return [self] + # @return [undefined] # # @api private # - def colorized_diff(original, current) + def colorized_diff(mutation) + if mutation.kind_of?(Mutation::Noop) + io.mutation.original_source + return + end + + original, current = mutation.original_source, mutation.source 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 + puts(diff) self end diff --git a/lib/mutant/reporter/null.rb b/lib/mutant/reporter/null.rb index b5a0b7f0..cb1c9617 100644 --- a/lib/mutant/reporter/null.rb +++ b/lib/mutant/reporter/null.rb @@ -1,7 +1,8 @@ module Mutant class Reporter - # Null reporter - Null = Class.new(self) do + + class Null < self + # Report subject # # @param [Subject] subject @@ -37,6 +38,7 @@ module Mutant def killer(*) self end - end.new.freeze + end + end end diff --git a/lib/mutant/reporter/stats.rb b/lib/mutant/reporter/stats.rb index 2789d537..048d58eb 100644 --- a/lib/mutant/reporter/stats.rb +++ b/lib/mutant/reporter/stats.rb @@ -10,7 +10,7 @@ module Mutant # # @api private # - attr_reader :subject + attr_reader :subjects # Return mutation count # @@ -18,7 +18,15 @@ module Mutant # # @api private # - attr_reader :mutation + attr_reader :mutations + + # Return skip count + # + # @return [Fixnum] + # + # @api private + # + attr_reader :noop_fails # Return kill count # @@ -26,7 +34,7 @@ module Mutant # # @api private # - attr_reader :kill + attr_reader :kills # Return mutation runtime # @@ -38,7 +46,7 @@ module Mutant def initialize @start = Time.now - @subject = @mutation = @kill = @time = 0 + @noop_fails = @subjects = @mutations = @kills = @time = 0 end def runtime @@ -46,19 +54,27 @@ module Mutant end def subject - @subject +=1 + @subjects +=1 + self end def alive - @mutation - @kill + @mutations - @kills + end + + def noop_fail(killer) + @noop_fails += 1 + @time += killer.runtime + self end def killer(killer) - @mutation +=1 - @kill +=1 unless killer.fail? + @mutations +=1 + @kills +=1 unless killer.fail? @time += killer.runtime + self end - end + end end end diff --git a/lib/mutant/runner.rb b/lib/mutant/runner.rb index a48d69ca..58b53e10 100644 --- a/lib/mutant/runner.rb +++ b/lib/mutant/runner.rb @@ -86,6 +86,7 @@ module Mutant # @api private # def run_subject(subject) + return unless noop(subject) subject.each do |mutation| next unless config.filter.match?(mutation) reporter.mutation(mutation) @@ -93,20 +94,58 @@ module Mutant end end + # Test for noop mutation + # + # @param [Subject] subject + # + # @return [true] + # if noop mutation is okay + # + # @return [false] + # otherwise + # + # @api private + # + def noop(subject) + killer = killer(subject.noop) + reporter.noop(killer) + unless killer.fail? + @errors << killer + false + end + + true + end + # Run killer on mutation # # @param [Mutation] mutation # - # @return [undefined] + # @return [true] + # if killer was unsuccessful + # + # @return [false] + # otherwise # # @api private # def kill(mutation) - killer = config.strategy.kill(mutation) + killer = killer(mutation) reporter.killer(killer) + if killer.fail? @errors << killer end end + + # Return killer for mutation + # + # @return [Killer] + # + # @api private + # + def killer(mutation) + config.strategy.kill(mutation) + end end end diff --git a/lib/mutant/strategy.rb b/lib/mutant/strategy.rb index fd283423..8032e59d 100644 --- a/lib/mutant/strategy.rb +++ b/lib/mutant/strategy.rb @@ -1,6 +1,46 @@ module Mutant class Strategy - include AbstractType + include AbstractType, Adamantium::Flat, Equalizer.new + + # Return config + # + # @return [Config] + # + # @api private + # + attr_reader :config + + # Initialize object + # + # @param [Config] config + # + # @return [undefined + # + # @api private + # + def initialize(config) + @config = config + end + + # Return output stream + # + # @return [IO] + # + # @api private + # + def output_stream + config.reporter.output_stream + end + + # Return error stream + # + # @return [IO] + # + # @api private + # + def error_stream + config.reporter.error_stream + end # Kill mutation # @@ -10,7 +50,7 @@ module Mutant # # @api private # - def self.kill(mutation) + def kill(mutation) killer.new(self, mutation) end @@ -20,12 +60,13 @@ module Mutant # # @api private # - def self.killer - self::KILLER + def killer + self.class::KILLER end # Static strategies class Static < self + include Equalizer.new # Always fail to kill strategy class Fail < self diff --git a/lib/mutant/strategy/rspec.rb b/lib/mutant/strategy/rspec.rb index a44e0ea2..918b1110 100644 --- a/lib/mutant/strategy/rspec.rb +++ b/lib/mutant/strategy/rspec.rb @@ -15,7 +15,7 @@ module Mutant # # @api private # - def self.spec_files(mutation) + def spec_files(mutation) ExampleLookup.run(mutation) end end @@ -29,7 +29,7 @@ module Mutant # # @api private # - def self.spec_files(mutation) + def spec_files(mutation) ['spec/unit'] end end @@ -43,14 +43,14 @@ module Mutant # # @api private # - def self.spec_files(mutation) + def spec_files(mutation) Dir['spec/integration/**/*_spec.rb'] end end # Run all specs per mutation class Full < self - def self.spec_files(mutation) + def spec_files(mutation) Dir['spec/**/*_spec.rb'] end end diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index 9709fb8d..ed47a559 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -46,6 +46,17 @@ module Mutant self end + # Return noop mutation + # + # @return [Mutation::Noop] + # + # @api private + # + def noop + Mutation::Noop.new(self) + end + memoize :noop + # Return subject identicication # # @return [String] diff --git a/spec/shared/mutator_behavior.rb b/spec/shared/mutator_behavior.rb index d3ee4536..e8cff33d 100644 --- a/spec/shared/mutator_behavior.rb +++ b/spec/shared/mutator_behavior.rb @@ -15,13 +15,25 @@ shared_examples_for 'a mutator' do it { should be_instance_of(to_enum.class) } + def assert_transitive(ast) + generated = ToSource.to_source(ast) + parsed = generated.to_ast + again = ToSource.to_source(parsed) + unless generated == again + fail "Untransitive:\n%s\n---\n%s" % [generated, again] + end + end + unless instance_methods.include?(:expected_mutations) let(:expected_mutations) do mutations.map do |mutation| case mutation when String - mutation.to_ast + ast = mutation.to_ast + assert_transitive(ast) + ast when Rubinius::AST::Node + assert_transitive(mutation) mutation else raise 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 b12b1411..244365f7 100644 --- a/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +++ b/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb @@ -4,7 +4,7 @@ describe Mutant::Killer::Rspec, '.new' do subject { object.new(strategy, mutation) } - let(:strategy) { mock('Strategy', :spec_files => ['foo']) } + let(:strategy) { mock('Strategy', :spec_files => ['foo'], :error_stream => $stderr, :output_stream => $stdout) } let(:context) { mock('Context') } let(:mutation) { mock('Mutation') } diff --git a/spec/unit/mutant/mutator/node/define/mutation_spec.rb b/spec/unit/mutant/mutator/node/define/mutation_spec.rb index a32ba58d..a1fd55d3 100644 --- a/spec/unit/mutant/mutator/node/define/mutation_spec.rb +++ b/spec/unit/mutant/mutator/node/define/mutation_spec.rb @@ -48,6 +48,25 @@ describe Mutant::Mutator, 'define' do mutations << 'def foo(a, b); Object.new; end' end + it_should_behave_like 'a mutator' + end + + context 'default argument' do + let(:source) { 'def foo(a = "literal"); end' } + + before do + Mutant::Random.stub(:hex_string => 'random') + end + + let(:mutations) do + mutations = [] + mutations << 'def foo(a); end' + mutations << 'def foo(); end' + mutations << 'def foo(a = "random"); end' + mutations << 'def foo(a = nil); end' + mutations << 'def foo(a = "literal"); Object.new; end' + mutations << 'def foo(srandom = "literal"); nil; end' + end it_should_behave_like 'a mutator' end diff --git a/spec/unit/mutant/mutator/node/send/mutation_spec.rb b/spec/unit/mutant/mutator/node/send/mutation_spec.rb index 90e5deaf..67c5964b 100644 --- a/spec/unit/mutant/mutator/node/send/mutation_spec.rb +++ b/spec/unit/mutant/mutator/node/send/mutation_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Mutant::Mutator, 'call' do +describe Mutant::Mutator, 'send' do context 'send without arguments' do # This could not be reproduced in a test case but happens in the mutant source code? context 'block_given?' do