From 41c41b2a1723f15eb84358cb61dea08ded7756c7 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sun, 6 Mar 2016 21:19:32 +0000 Subject: [PATCH] Fix coverage of Mutant::Mutator namespace --- Rakefile | 3 +- config/flay.yml | 4 +- lib/mutant.rb | 2 - lib/mutant/ast/meta/send.rb | 1 + lib/mutant/meta/example.rb | 2 +- lib/mutant/mutator.rb | 66 ++++--------------- lib/mutant/mutator/node.rb | 34 +++++----- lib/mutant/mutator/node/arguments.rb | 12 ++-- lib/mutant/mutator/node/begin.rb | 17 +---- lib/mutant/mutator/node/blockarg.rb | 13 ---- lib/mutant/mutator/node/break.rb | 2 +- lib/mutant/mutator/node/dstr.rb | 2 +- lib/mutant/mutator/node/dsym.rb | 2 +- lib/mutant/mutator/node/generic.rb | 4 +- lib/mutant/mutator/node/kwbegin.rb | 2 +- lib/mutant/mutator/node/literal.rb | 9 +++ lib/mutant/mutator/node/literal/fixnum.rb | 2 +- lib/mutant/mutator/node/literal/float.rb | 8 +-- lib/mutant/mutator/node/literal/regex.rb | 4 +- lib/mutant/mutator/node/literal/symbol.rb | 4 +- lib/mutant/mutator/node/mlhs.rb | 5 +- .../node/named_value/constant_assignment.rb | 5 +- .../node/named_value/variable_assignment.rb | 4 +- lib/mutant/mutator/node/next.rb | 2 +- lib/mutant/mutator/node/resbody.rb | 2 +- lib/mutant/mutator/node/rescue.rb | 4 +- lib/mutant/mutator/node/restarg.rb | 13 ---- lib/mutant/mutator/node/send.rb | 3 +- .../mutator/node/send/attribute_assignment.rb | 5 +- lib/mutant/mutator/node/when.rb | 2 +- lib/mutant/mutator/node/yield.rb | 2 +- lib/mutant/mutator/registry.rb | 25 ++++--- lib/mutant/mutator/util.rb | 30 --------- lib/mutant/mutator/util/array.rb | 14 +--- lib/mutant/subject.rb | 2 +- meta/casgn.rb | 9 +++ meta/const.rb | 8 +++ meta/if.rb | 14 ++++ meta/ivasgn.rb | 13 ++++ meta/match_current_line.rb | 2 +- meta/nthref.rb | 10 +-- meta/regex.rb | 34 ++++++++-- meta/rescue.rb | 45 +++++++++++++ meta/restarg.rb | 13 ++-- meta/send.rb | 20 +++--- .../mutant/test_mutator_handles_types_spec.rb | 3 +- spec/support/corpus.rb | 6 +- spec/unit/mutant/mutator/node_spec.rb | 41 ++++++++++-- spec/unit/mutant/mutator/registry_spec.rb | 40 +++++++---- spec/unit/mutant/mutator_spec.rb | 21 ++++++ spec/unit/mutant/subject_spec.rb | 6 +- 51 files changed, 331 insertions(+), 265 deletions(-) delete mode 100644 lib/mutant/mutator/node/blockarg.rb delete mode 100644 lib/mutant/mutator/node/restarg.rb create mode 100644 spec/unit/mutant/mutator_spec.rb diff --git a/Rakefile b/Rakefile index 6a30d31c1..aebe5ca78 100644 --- a/Rakefile +++ b/Rakefile @@ -9,14 +9,13 @@ namespace :metrics do task mutant: :coverage do arguments = %w[ bundle exec mutant - --ignore-subject Mutant::Meta* --include lib --since HEAD~1 --require mutant --use rspec --zombie ] - arguments.concat(%w[--jobs 4]) if ENV.key?('CIRCLE_CI') + arguments.concat(%w[--jobs 4]) if ENV.key?('CIRCLECI') arguments.concat(%w[-- Mutant*]) diff --git a/config/flay.yml b/config/flay.yml index 06b115ffa..d3f0e9fc5 100644 --- a/config/flay.yml +++ b/config/flay.yml @@ -1,3 +1,3 @@ --- -threshold: 18 -total_score: 1162 +threshold: 16 +total_score: 1160 diff --git a/lib/mutant.rb b/lib/mutant.rb index 771749377..d917da52c 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -91,7 +91,6 @@ def self.ci? require 'mutant/mutator/node/literal/nil' require 'mutant/mutator/node/argument' require 'mutant/mutator/node/arguments' -require 'mutant/mutator/node/blockarg' require 'mutant/mutator/node/begin' require 'mutant/mutator/node/binary' require 'mutant/mutator/node/const' @@ -112,7 +111,6 @@ def self.ci? require 'mutant/mutator/node/yield' require 'mutant/mutator/node/super' require 'mutant/mutator/node/zsuper' -require 'mutant/mutator/node/restarg' require 'mutant/mutator/node/send' require 'mutant/mutator/node/send/binary' require 'mutant/mutator/node/send/conditional' diff --git a/lib/mutant/ast/meta/send.rb b/lib/mutant/ast/meta/send.rb index 9689f58ee..1d76c75af 100644 --- a/lib/mutant/ast/meta/send.rb +++ b/lib/mutant/ast/meta/send.rb @@ -18,6 +18,7 @@ class Send # # @return [Enumerable] alias_method :arguments, :remaining_children + public :arguments # Test if AST node is a valid assignment target diff --git a/lib/mutant/meta/example.rb b/lib/mutant/meta/example.rb index 13026e35b..84ceff630 100644 --- a/lib/mutant/meta/example.rb +++ b/lib/mutant/meta/example.rb @@ -22,7 +22,7 @@ def source # # @return [Enumerable] def generated - Mutator.each(node).map do |node| + Mutator::REGISTRY.call(node).map do |node| Mutation::Evil.new(self, node) end end diff --git a/lib/mutant/mutator.rb b/lib/mutant/mutator.rb index f4cdffd2b..08e8ce2d1 100644 --- a/lib/mutant/mutator.rb +++ b/lib/mutant/mutator.rb @@ -1,42 +1,22 @@ module Mutant # Generator for mutations class Mutator - include Adamantium::Flat, AbstractType - - # Run mutator on input - # - # @param [Object] input - # the input to mutate - # - # @param [Mutator] parent - # - # @return [self] - def self.each(input, parent = nil, &block) - return to_enum(__method__, input, parent) unless block_given? - REGISTRY.lookup(input).new(input, parent, block) - - self - end + include Adamantium::Flat, Concord.new(:input, :parent), AbstractType, Procto.call(:output) # Register node class handler # # @return [undefined] def self.handle(*types) types.each do |type| - REGISTRY.register(type, self) + self::REGISTRY.register(type, self) end end private_class_method :handle - # Mutation input - # - # @return [Object] - attr_reader :input - - # Parent context of input + # Return output # - # @return [Object] - attr_reader :parent + # @return [Set] + attr_reader :output private @@ -47,10 +27,11 @@ def self.handle(*types) # @param [#call(node)] block # # @return [undefined] - def initialize(input, parent, block) - @input, @parent, @block = input, parent, block - @seen = Set.new - guard(input) + def initialize(_input, _parent = nil) + super + + @output = Set.new + dispatch end @@ -60,16 +41,7 @@ def initialize(input, parent, block) # # @return [Boolean] def new?(object) - !@seen.include?(object) - end - - # Add object to guarded values - # - # @param [Object] object - # - # @return [undefined] - def guard(object) - @seen << object + !object.eql?(input) end # Dispatch node generations @@ -85,26 +57,14 @@ def guard(object) def emit(object) return unless new?(object) - guard(object) - - emit!(object) - end - - # Call block with node - # - # @param [Parser::AST::Node] node - # - # @return [self] - def emit!(node) - @block.call(node) - self + output << object end # Run input with mutator # # @return [undefined] def run(mutator) - mutator.new(input, self, method(:emit)) + mutator.call(input).each(&method(:emit)) end # Shortcut to create a new unfrozen duplicate of input diff --git a/lib/mutant/mutator/node.rb b/lib/mutant/mutator/node.rb index a16413def..0f62c9898 100644 --- a/lib/mutant/mutator/node.rb +++ b/lib/mutant/mutator/node.rb @@ -20,11 +20,11 @@ class Node < self def self.define_named_child(name, index) super - define_method("emit_#{name}_mutations") do |&block| + define_method(:"emit_#{name}_mutations") do |&block| mutate_child(index, &block) end - define_method("emit_#{name}") do |node| + define_method(:"emit_#{name}") do |node| emit_child_update(index, node) end end @@ -56,10 +56,9 @@ def children # @return [undefined] # # rubocop:disable RedundantBlockCall - its not redundant here - def mutate_child(index, mutator = Mutator, &block) + def mutate_child(index, &block) block ||= TAUTOLOGY - child = children.at(index) - mutator.each(child, self) do |mutation| + REGISTRY.call(children.fetch(index), self).each do |mutation| next unless block.call(mutation) emit_child_update(index, mutation) end @@ -119,17 +118,6 @@ def emit_nil emit(N_NIL) unless asgn_left? end - # Emit values - # - # @param [Array] values - # - # @return [undefined] - def emit_values(values) - values.each do |value| - emit_type(value) - end - end - # Parent node # # @return [Parser::AST::Node] node @@ -165,9 +153,17 @@ def asgn_left? # # @return [Enumerable] def children_indices(range) - range_end = range.end - last_index = range_end >= 0 ? range_end : children.length + range_end - range.begin.upto(last_index) + range.begin.upto(children.length + range.end) + end + + # Emit single child mutation + # + # @return [undefined] + def mutate_single_child + children.each_with_index do |child, index| + mutate_child(index) + yield child, index unless children.one? + end end end # Node diff --git a/lib/mutant/mutator/node/arguments.rb b/lib/mutant/mutator/node/arguments.rb index de0a67bc5..910b3d5f6 100644 --- a/lib/mutant/mutator/node/arguments.rb +++ b/lib/mutant/mutator/node/arguments.rb @@ -22,7 +22,7 @@ def dispatch # @return [undefined] def emit_argument_presence emit_type - Mutator::Util::Array::Presence.each(children, self) do |children| + Util::Array::Presence.call(children).each do |children| emit_type(*children) end end @@ -32,7 +32,7 @@ def emit_argument_presence # @return [undefined] def emit_argument_mutations children.each_with_index do |child, index| - Mutator.each(child) do |mutant| + REGISTRY.call(child).each do |mutant| next if invalid_argument_replacement?(mutant, index) emit_child_update(index, mutant) end @@ -45,11 +45,7 @@ def emit_argument_mutations # # @return [Boolean] def invalid_argument_replacement?(mutant, index) - original = children.fetch(index) - - n_optarg?(original) && - n_arg?(mutant) && - children[0...index].any?(&method(:n_optarg?)) + n_arg?(mutant) && children[0...index].any?(&method(:n_optarg?)) end # Emit mlhs expansions @@ -59,7 +55,7 @@ def emit_mlhs_expansion mlhs_childs_with_index.each do |child, index| dup_children = children.dup dup_children.delete_at(index) - dup_children.insert(index, *child.children) + dup_children.insert(index, *child) emit_type(*dup_children) end end diff --git a/lib/mutant/mutator/node/begin.rb b/lib/mutant/mutator/node/begin.rb index 772b25a28..3fcba15c7 100644 --- a/lib/mutant/mutator/node/begin.rb +++ b/lib/mutant/mutator/node/begin.rb @@ -13,23 +13,10 @@ class Begin < self # # @return [undefined] def dispatch - Util::Array.each(children, self, &method(:emit_child_subset)) - children.each_with_index do |child, index| - mutate_child(index) - emit(child) unless children.eql?([child]) + mutate_single_child do |child| + emit(child) end end - - # Emit child subset - # - # @param [Array] children - # - # @return [undefined] - def emit_child_subset(children) - return if children.length < 2 - emit_type(*children) - end - end # Block end # Node end # Mutator diff --git a/lib/mutant/mutator/node/blockarg.rb b/lib/mutant/mutator/node/blockarg.rb deleted file mode 100644 index 487b27c7f..000000000 --- a/lib/mutant/mutator/node/blockarg.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Mutant - class Mutator - class Node - - # Blockarg mutator - class Blockarg < Generic - - handle(:blockarg) - - end # Blockarg - end # Node - end # Mutator -end # Mutant diff --git a/lib/mutant/mutator/node/break.rb b/lib/mutant/mutator/node/break.rb index b04d490de..4ad86df09 100644 --- a/lib/mutant/mutator/node/break.rb +++ b/lib/mutant/mutator/node/break.rb @@ -13,7 +13,7 @@ class Break < Generic # # @return [undefined] def dispatch - super + super() emit_singletons children.each_index(&method(:delete_child)) end diff --git a/lib/mutant/mutator/node/dstr.rb b/lib/mutant/mutator/node/dstr.rb index 9d45738c6..0a6335e5d 100644 --- a/lib/mutant/mutator/node/dstr.rb +++ b/lib/mutant/mutator/node/dstr.rb @@ -13,7 +13,7 @@ class Dstr < Generic # # @return [undefined] def dispatch - super + super() emit_singletons end diff --git a/lib/mutant/mutator/node/dsym.rb b/lib/mutant/mutator/node/dsym.rb index 7f118fd49..973d88780 100644 --- a/lib/mutant/mutator/node/dsym.rb +++ b/lib/mutant/mutator/node/dsym.rb @@ -13,7 +13,7 @@ class Dsym < Generic # # @return [undefined] def dispatch - super + super() emit_singletons end diff --git a/lib/mutant/mutator/node/generic.rb b/lib/mutant/mutator/node/generic.rb index 78aa6c311..ba28195af 100644 --- a/lib/mutant/mutator/node/generic.rb +++ b/lib/mutant/mutator/node/generic.rb @@ -8,9 +8,9 @@ class Generic < self # These nodes still need a dedicated mutator, # your contribution is that close! handle( - :ensure, :redo, :regopt, :retry, :arg_expr, + :ensure, :redo, :regopt, :retry, :arg_expr, :blockarg, :kwrestarg, :kwoptarg, :kwarg, :undef, :module, :empty, - :alias, :for, :xstr, :back_ref, :class, + :alias, :for, :xstr, :back_ref, :class, :restarg, :sclass, :match_with_lvasgn, :while_post, :until_post, :preexe, :postexe, :iflipflop, :eflipflop, :kwsplat, :shadowarg, :rational, :complex, :__FILE__, :__LINE__ diff --git a/lib/mutant/mutator/node/kwbegin.rb b/lib/mutant/mutator/node/kwbegin.rb index 9d51ec209..1ad6185ed 100644 --- a/lib/mutant/mutator/node/kwbegin.rb +++ b/lib/mutant/mutator/node/kwbegin.rb @@ -13,7 +13,7 @@ class Kwbegin < Generic # # @return [undefined] def dispatch - super + super() emit_singletons end diff --git a/lib/mutant/mutator/node/literal.rb b/lib/mutant/mutator/node/literal.rb index 46f558d49..878312849 100644 --- a/lib/mutant/mutator/node/literal.rb +++ b/lib/mutant/mutator/node/literal.rb @@ -4,6 +4,15 @@ class Node # Abstract mutator for literal AST nodes class Literal < self include AbstractType + + private + + # Emit values + # + # @return [undefined] + def emit_values + values.each(&method(:emit_type)) + end end # Literal end # Node end # Mutator diff --git a/lib/mutant/mutator/node/literal/fixnum.rb b/lib/mutant/mutator/node/literal/fixnum.rb index b9d108557..25d2914b7 100644 --- a/lib/mutant/mutator/node/literal/fixnum.rb +++ b/lib/mutant/mutator/node/literal/fixnum.rb @@ -14,7 +14,7 @@ class Fixnum < self # @return [undefined] def dispatch emit_singletons - emit_values(values) + emit_values end # Values to mutate to diff --git a/lib/mutant/mutator/node/literal/float.rb b/lib/mutant/mutator/node/literal/float.rb index f060f9cc7..41d092a6b 100644 --- a/lib/mutant/mutator/node/literal/float.rb +++ b/lib/mutant/mutator/node/literal/float.rb @@ -14,7 +14,7 @@ class Float < self # @return [undefined] def dispatch emit_singletons - emit_values(values) + emit_values emit_special_cases end @@ -36,10 +36,8 @@ def emit_special_cases # @return [Array] def values original = children.first - # Work around a bug in RBX/MRI or JRUBY: - [0.0, 1.0, -original].delete_if do |value| - value.eql?(original) - end + + [0.0, 1.0, -original] end end # Float diff --git a/lib/mutant/mutator/node/literal/regex.rb b/lib/mutant/mutator/node/literal/regex.rb index 1f17f7a3f..378fba603 100644 --- a/lib/mutant/mutator/node/literal/regex.rb +++ b/lib/mutant/mutator/node/literal/regex.rb @@ -8,7 +8,7 @@ class Regex < self handle(:regexp) # No input can ever be matched with this - NULL_REGEXP_SOURCE = 'a\A'.freeze + NULL_REGEXP_SOURCE = 'nomatch\A'.freeze private @@ -23,7 +23,7 @@ def options # # @return [undefined] def dispatch - emit_singletons unless parent_node && n_match_current_line?(parent_node) + emit_singletons unless parent_node children.each_with_index do |child, index| mutate_child(index) unless n_str?(child) end diff --git a/lib/mutant/mutator/node/literal/symbol.rb b/lib/mutant/mutator/node/literal/symbol.rb index 3451620be..41b875494 100644 --- a/lib/mutant/mutator/node/literal/symbol.rb +++ b/lib/mutant/mutator/node/literal/symbol.rb @@ -18,9 +18,7 @@ class Symbol < self # @return [undefined] def dispatch emit_singletons - Mutator::Util::Symbol.each(value, self) do |value| - emit_type(value) - end + Util::Symbol.call(value).each(&method(:emit_type)) end end # Symbol diff --git a/lib/mutant/mutator/node/mlhs.rb b/lib/mutant/mutator/node/mlhs.rb index 75fe0f1ec..1ef1b899b 100644 --- a/lib/mutant/mutator/node/mlhs.rb +++ b/lib/mutant/mutator/node/mlhs.rb @@ -12,9 +12,8 @@ class MLHS < self # # @return [undefined] def dispatch - children.each_index do |index| - mutate_child(index) - delete_child(index) unless children.one? + mutate_single_child do |_child, index| + delete_child(index) end end diff --git a/lib/mutant/mutator/node/named_value/constant_assignment.rb b/lib/mutant/mutator/node/named_value/constant_assignment.rb index 005105d34..0bd1054da 100644 --- a/lib/mutant/mutator/node/named_value/constant_assignment.rb +++ b/lib/mutant/mutator/node/named_value/constant_assignment.rb @@ -17,7 +17,8 @@ class ConstantAssignment < Node # @return [undefined] def dispatch mutate_name - emit_value_mutations if value + return unless value # op asgn + emit_value_mutations emit_remove_const end @@ -32,7 +33,7 @@ def emit_remove_const # # @return [undefined] def mutate_name - Mutator::Util::Symbol.each(name, self) do |name| + Util::Symbol.call(name).each do |name| emit_name(name.upcase) end end diff --git a/lib/mutant/mutator/node/named_value/variable_assignment.rb b/lib/mutant/mutator/node/named_value/variable_assignment.rb index c578b9af0..4a7aa6936 100644 --- a/lib/mutant/mutator/node/named_value/variable_assignment.rb +++ b/lib/mutant/mutator/node/named_value/variable_assignment.rb @@ -29,7 +29,7 @@ class VariableAssignment < Node def dispatch emit_singletons mutate_name - emit_value_mutations if value # mlhs! + emit_value_mutations if value # op asgn! end # Emit name mutations @@ -38,7 +38,7 @@ def dispatch def mutate_name prefix, regexp = MAP.fetch(node.type) stripped = name.to_s.sub(regexp, EMPTY_STRING) - Mutator::Util::Symbol.each(stripped, self) do |name| + Util::Symbol.call(stripped).each do |name| emit_name(:"#{prefix}#{name}") end end diff --git a/lib/mutant/mutator/node/next.rb b/lib/mutant/mutator/node/next.rb index 5e1dc62b0..7791b7177 100644 --- a/lib/mutant/mutator/node/next.rb +++ b/lib/mutant/mutator/node/next.rb @@ -13,7 +13,7 @@ class Next < Generic # # @return [undefined] def dispatch - super + super() emit_singletons children.each_index(&method(:delete_child)) emit(s(:break, *children)) diff --git a/lib/mutant/mutator/node/resbody.rb b/lib/mutant/mutator/node/resbody.rb index ac4add8c0..03025f051 100644 --- a/lib/mutant/mutator/node/resbody.rb +++ b/lib/mutant/mutator/node/resbody.rb @@ -24,7 +24,7 @@ def dispatch # @return [undefined] def mutate_captures return unless captures - Util::Array::Element.each(captures.children, self) do |matchers| + Util::Array::Element.call(captures.children).each do |matchers| next if matchers.any?(&method(:n_nil?)) emit_captures(s(:array, *matchers)) end diff --git a/lib/mutant/mutator/node/rescue.rb b/lib/mutant/mutator/node/rescue.rb index 2bd120d6e..33c4adf3b 100644 --- a/lib/mutant/mutator/node/rescue.rb +++ b/lib/mutant/mutator/node/rescue.rb @@ -28,10 +28,8 @@ def dispatch # @return [undefined] def mutate_rescue_bodies children_indices(RESCUE_INDICES).each do |index| - rescue_body = children.at(index) - next unless rescue_body mutate_child(index) - resbody_body = AST::Meta::Resbody.new(rescue_body).body + resbody_body = AST::Meta::Resbody.new(children.fetch(index)).body emit_concat(resbody_body) if resbody_body end end diff --git a/lib/mutant/mutator/node/restarg.rb b/lib/mutant/mutator/node/restarg.rb deleted file mode 100644 index 8a32c355a..000000000 --- a/lib/mutant/mutator/node/restarg.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Mutant - class Mutator - class Node - - # Restarg mutator - class Restarg < Generic - - handle(:restarg) - - end # Restarg - end # Node - end # Mutator -end # Mutant diff --git a/lib/mutant/mutator/node/send.rb b/lib/mutant/mutator/node/send.rb index 4edefa274..6829e10ab 100644 --- a/lib/mutant/mutator/node/send.rb +++ b/lib/mutant/mutator/node/send.rb @@ -83,6 +83,7 @@ def meta # # @return [Enumerable] alias_method :arguments, :remaining_children + private :arguments # Perform normal, non special case dispatch # @@ -165,7 +166,7 @@ def emit_selector_replacement # # @return [undefined] def emit_naked_receiver - emit(receiver) if receiver && !NOT_ASSIGNABLE.include?(receiver.type) + emit(receiver) if receiver end # Mutate arguments diff --git a/lib/mutant/mutator/node/send/attribute_assignment.rb b/lib/mutant/mutator/node/send/attribute_assignment.rb index 73c63d31f..037f66d5d 100644 --- a/lib/mutant/mutator/node/send/attribute_assignment.rb +++ b/lib/mutant/mutator/node/send/attribute_assignment.rb @@ -5,6 +5,9 @@ class Send # Mutator for attribute assignments class AttributeAssignment < self + ATTRIBUTE_RANGE = (0..-2).freeze + private_constant(*constants(false)) + private # Emit mutations @@ -28,7 +31,7 @@ def mutate_arguments # # @return [undefined] def emit_attribute_read - emit_type(receiver, selector.to_s[0..-2].to_sym) + emit_type(receiver, selector[ATTRIBUTE_RANGE].to_sym) end end # AttributeAssignment diff --git a/lib/mutant/mutator/node/when.rb b/lib/mutant/mutator/node/when.rb index 16eb2a4c9..d61ba1505 100644 --- a/lib/mutant/mutator/node/when.rb +++ b/lib/mutant/mutator/node/when.rb @@ -47,7 +47,7 @@ def mutate_body # @return [nil] # otherwise def body - children[body_index] + children.fetch(body_index) end # Index of body node diff --git a/lib/mutant/mutator/node/yield.rb b/lib/mutant/mutator/node/yield.rb index 1669917d8..2683ead44 100644 --- a/lib/mutant/mutator/node/yield.rb +++ b/lib/mutant/mutator/node/yield.rb @@ -13,7 +13,7 @@ class Yield < Generic # # @return [undefined] def dispatch - super + super() emit_singletons children.each_index(&method(:delete_child)) end diff --git a/lib/mutant/mutator/registry.rb b/lib/mutant/mutator/registry.rb index a61c8f6e5..1e52d355c 100644 --- a/lib/mutant/mutator/registry.rb +++ b/lib/mutant/mutator/registry.rb @@ -2,12 +2,13 @@ module Mutant class Mutator # Registry for mutators class Registry + include Concord.new(:contents) # Initialize object # # @return [undefined] def initialize - @registry = {} + super({}) end # Raised when the type is an invalid type @@ -20,23 +21,31 @@ def initialize # # @return [self] def register(type, mutator) - fail RegistryError, "Invalid type registration: #{type}" unless AST::Types::ALL.include?(type) - fail RegistryError, "Duplicate type registration: #{type}" if @registry.key?(type) - @registry[type] = mutator + fail RegistryError, "Invalid type registration: #{type.inspect}" unless AST::Types::ALL.include?(type) + fail RegistryError, "Duplicate type registration: #{type.inspect}" if contents.key?(type) + contents[type] = mutator self end + # Call registry + # + # @return [Enumerable] + def call(node, parent = nil) + lookup(node.type).call(node, parent) + end + + private + # Lookup mutator class for node # - # @param [Parser::AST::Node] node + # @param [Symbol] type # # @return [Class] # # @raise [ArgumentError] # raises argument error when mutator class cannot be found - def lookup(node) - type = node.type - @registry.fetch(type) do + def lookup(type) + contents.fetch(type) do fail RegistryError, "No mutator to handle: #{type.inspect}" end end diff --git a/lib/mutant/mutator/util.rb b/lib/mutant/mutator/util.rb index 8c5f11bc4..9d219b793 100644 --- a/lib/mutant/mutator/util.rb +++ b/lib/mutant/mutator/util.rb @@ -2,36 +2,6 @@ module Mutant class Mutator # Namespace for utility mutators class Util < self - - # Run utility mutator - # - # @param [Object] object - # @param [Object] parent - # - # @return [Enumerator] - # if no block given - # - # @return [self] - # otherwise - def self.each(object, parent, &block) - return to_enum(__method__, object, parent) unless block_given? - - new(object, parent, block) - - self - end - - private - - # Test if mutation is new - # - # @param [Object] generated - # - # @return [Boolean] - def new?(generated) - !input.eql?(generated) - end - end # Util end # Mutator end # Mutant diff --git a/lib/mutant/mutator/util/array.rb b/lib/mutant/mutator/util/array.rb index 3c3fb4896..3195b3ad3 100644 --- a/lib/mutant/mutator/util/array.rb +++ b/lib/mutant/mutator/util/array.rb @@ -33,7 +33,7 @@ class Element < Util # @return [undefined] def dispatch input.each_with_index do |element, index| - Mutator.each(element).each do |mutation| + REGISTRY.call(element).each do |mutation| dup = dup_input dup[index] = mutation emit(dup) @@ -42,18 +42,6 @@ def dispatch end end # Element - - private - - # Emit mutations - # - # @return [undefined] - def dispatch - run(Element) - run(Presence) - emit(EMPTY_ARRAY) - end - end # Array end # Node end # Mutant diff --git a/lib/mutant/subject.rb b/lib/mutant/subject.rb index 54e61d3ff..adfe793e3 100644 --- a/lib/mutant/subject.rb +++ b/lib/mutant/subject.rb @@ -10,7 +10,7 @@ class Subject # @return [undefined] def mutations [neutral_mutation].concat( - Mutator.each(node).map do |mutant| + Mutator::REGISTRY.call(node).map do |mutant| Mutation::Evil.new(self, wrap_node(mutant)) end ) diff --git a/meta/casgn.rb b/meta/casgn.rb index be3662619..9ad300d7f 100644 --- a/meta/casgn.rb +++ b/meta/casgn.rb @@ -15,3 +15,12 @@ mutation 'self::A = nil' mutation 'self.remove_const :A' end + +Mutant::Meta::Example.add :casgn do + source 'A &&= true' + + singleton_mutations + mutation 'A__MUTANT__ &&= true' + mutation 'A &&= false' + mutation 'A &&= nil' +end diff --git a/meta/const.rb b/meta/const.rb index 936a15d28..79fe9caae 100644 --- a/meta/const.rb +++ b/meta/const.rb @@ -5,3 +5,11 @@ mutation 'B::C' mutation 'C' end + +Mutant::Meta::Example.add :const do + source 'A.foo' + + singleton_mutations + mutation 'A' + mutation 'self.foo' +end diff --git a/meta/if.rb b/meta/if.rb index 064e7119c..429418411 100644 --- a/meta/if.rb +++ b/meta/if.rb @@ -59,3 +59,17 @@ mutation 'if condition; true; end' mutation 'true' end + +Mutant::Meta::Example.add :if do + source 'true if /foo/' + + singleton_mutations + mutation 'false if /foo/' + mutation 'true if //' + mutation 'nil if /foo/' + mutation 'true if true' + mutation 'true if false' + mutation 'true if nil' + mutation 'true if /nomatch\A/' + mutation 'true' +end diff --git a/meta/ivasgn.rb b/meta/ivasgn.rb index 26e010df5..0fd5ab7bf 100644 --- a/meta/ivasgn.rb +++ b/meta/ivasgn.rb @@ -6,3 +6,16 @@ mutation '@a = false' mutation '@a = nil' end + +Mutant::Meta::Example.add :ivasgn do + source '@a &&= 1' + + singleton_mutations + + mutation '@a__mutant__ &&= 1' + mutation '@a &&= nil' + mutation '@a &&= 0' + mutation '@a &&= -1' + mutation '@a &&= 2' + mutation '@a &&= self' +end diff --git a/meta/match_current_line.rb b/meta/match_current_line.rb index 7a8acbb9e..4b2b812d5 100644 --- a/meta/match_current_line.rb +++ b/meta/match_current_line.rb @@ -8,6 +8,6 @@ mutation 'true if true' mutation 'true if false' mutation 'true if nil' - mutation 'true if /a\A/' + mutation 'true if /nomatch\A/' mutation 'true' end diff --git a/meta/nthref.rb b/meta/nthref.rb index 5fe0b36fa..8d1821756 100644 --- a/meta/nthref.rb +++ b/meta/nthref.rb @@ -1,12 +1,12 @@ -Mutant::Meta::Example.add :nthref do +Mutant::Meta::Example.add :nth_ref do source '$1' mutation '$2' end -Mutant::Meta::Example.add :nthref do - source '$2' +Mutant::Meta::Example.add :nth_ref do + source '$3' - mutation '$3' - mutation '$1' + mutation '$2' + mutation '$4' end diff --git a/meta/regex.rb b/meta/regex.rb index 930824f04..e62bcf281 100644 --- a/meta/regex.rb +++ b/meta/regex.rb @@ -2,18 +2,44 @@ source '/foo/' singleton_mutations - mutation '//' # match all - mutation '/a\A/' # match nothing + + # match all inputs + mutation '//' + + # match no input + mutation '/nomatch\A/' end Mutant::Meta::Example.add :regexp do source '/#{foo.bar}n/' singleton_mutations - mutation '//' # match all mutation '/#{foo}n/' - mutation '/a\A/' # match nothing mutation '/#{self.bar}n/' mutation '/#{nil}n/' mutation '/#{self}n/' + + # match all inputs + mutation '//' + + # match no input + mutation '/nomatch\A/' +end + +Mutant::Meta::Example.add :regexp do + source 'true if /foo/' + + singleton_mutations + mutation 'false if /foo/' + mutation 'nil if /foo/' + mutation 'true if true' + mutation 'true if false' + mutation 'true if nil' + mutation 'true' + + # match all inputs + mutation 'true if //' + + # match no input + mutation 'true if /nomatch\A/' end diff --git a/meta/rescue.rb b/meta/rescue.rb index 25d00ee13..03369718b 100644 --- a/meta/rescue.rb +++ b/meta/rescue.rb @@ -40,3 +40,48 @@ mutation 'begin; rescue; nil; end' mutation 'begin; true end' end + +Mutant::Meta::Example.add :rescue do + source 'begin; true; end' + + singleton_mutations + mutation 'begin; false; end' + mutation 'begin; nil; end' +end + +Mutant::Meta::Example.add :rescue do + source 'def a; foo; rescue; bar; else; baz; end' + + # Mutate all bodies + mutation 'def a; nil; rescue; bar; else; baz; end' + mutation 'def a; self; rescue; bar; else; baz; end' + mutation 'def a; foo; rescue; nil; else; baz; end' + mutation 'def a; foo; rescue; self; else; baz; end' + mutation 'def a; foo; rescue; bar; else; nil; end' + mutation 'def a; foo; rescue; bar; else; self; end' + + # Promote and concat rescue resbody bodies + mutation 'def a; foo; bar; end' + + # Promote and concat else body + mutation 'def a; foo; baz; end' + + # Promote rescue body + mutation 'def a; foo; end' + + # Empty body + mutation 'def a; end' + + # Failing body + mutation 'def a; raise; end' + + mutation 'remove_method :a' +end + +Mutant::Meta::Example.add :rescue do + source 'begin; rescue; ensure; true; end' + + singleton_mutations + mutation 'begin; rescue; ensure; false; end' + mutation 'begin; rescue; ensure; nil; end' +end diff --git a/meta/restarg.rb b/meta/restarg.rb index 9e6c00c78..34a218497 100644 --- a/meta/restarg.rb +++ b/meta/restarg.rb @@ -1,11 +1,8 @@ Mutant::Meta::Example.add :restarg do - source 'foo(*bar)' + source 'def foo(*bar); end' - singleton_mutations - mutation 'foo' - mutation 'foo(nil)' - mutation 'foo(self)' - mutation 'foo(*self)' - mutation 'foo(bar)' - mutation 'foo(*nil)' + mutation 'def foo; end' + mutation 'def foo(*bar); bar = []; end' + mutation 'def foo(*bar); raise; end' + mutation 'remove_method(:foo)' end diff --git a/meta/send.rb b/meta/send.rb index c74ab13eb..36ae60577 100644 --- a/meta/send.rb +++ b/meta/send.rb @@ -316,27 +316,25 @@ end Mutant::Meta::Example.add :send do - source 'self.bar = baz' + source 'self.booz = baz' singleton_mutations - mutation 'self.bar = nil' - mutation 'self.bar = self' - mutation 'self.bar' + mutation 'self.booz = nil' + mutation 'self.booz = self' + mutation 'self.booz' mutation 'baz' - # This one could probably be removed end Mutant::Meta::Example.add :send do - source 'foo.bar = baz' + source 'foo.booz = baz' singleton_mutations mutation 'foo' - mutation 'foo.bar = nil' - mutation 'foo.bar = self' - mutation 'self.bar = baz' - mutation 'foo.bar' + mutation 'foo.booz = nil' + mutation 'foo.booz = self' + mutation 'self.booz = baz' + mutation 'foo.booz' mutation 'baz' - # This one could probably be removed end Mutant::Meta::Example.add :send do diff --git a/spec/integration/mutant/test_mutator_handles_types_spec.rb b/spec/integration/mutant/test_mutator_handles_types_spec.rb index 9042ac34b..6f8c8518d 100644 --- a/spec/integration/mutant/test_mutator_handles_types_spec.rb +++ b/spec/integration/mutant/test_mutator_handles_types_spec.rb @@ -1,8 +1,7 @@ RSpec.describe 'AST type coverage', mutant: false do - specify 'mutant should not crash for any node parser can generate' do Mutant::AST::Types::ALL.each do |type| - Mutant::Mutator::REGISTRY.lookup(s(type)) + Mutant::Mutator::REGISTRY.__send__(:lookup, type) end end end diff --git a/spec/support/corpus.rb b/spec/support/corpus.rb index 3a5c0a809..4fc8c288b 100644 --- a/spec/support/corpus.rb +++ b/spec/support/corpus.rb @@ -88,11 +88,11 @@ def verify_mutation_generation rescue EncodingError, ArgumentError nil # Make rubocop happy end + if node - Mutant::Mutator::Node.each(node) do - count += 1 - end + count += Mutant::Mutator::REGISTRY.call(node).length end + count end.inject(0, :+) took = Time.now - start diff --git a/spec/unit/mutant/mutator/node_spec.rb b/spec/unit/mutant/mutator/node_spec.rb index 06f281a58..d006b921c 100644 --- a/spec/unit/mutant/mutator/node_spec.rb +++ b/spec/unit/mutant/mutator/node_spec.rb @@ -1,7 +1,9 @@ -RSpec.describe Mutant::Mutator::Node do - Mutant::Meta::Example::ALL.each do |example| - context "on #{example.node.type.inspect}" do - it 'generates the correct mutations' do +Mutant::Meta::Example::ALL.each.group_by(&:node_type).each do |type, examples| + RSpec.describe Mutant::Mutator::REGISTRY.__send__(:lookup, type) do + toplevel_nodes = examples.map { |example| example.node.type }.uniq + + it "generates the correct mutations on #{toplevel_nodes} toplevel examples" do + examples.each do |example| verification = example.verification unless verification.success? fail verification.error_report @@ -10,3 +12,34 @@ end end end + +RSpec.describe Mutant::Mutator::Node do + describe 'internal DSL' do + let(:klass) do + Class.new(described_class) do + children(:left, :right) + + def dispatch + left + emit_left(s(:nil)) + emit_right_mutations do |node| + node.eql?(s(:nil)) + end + end + end + end + + def apply + klass.call(s(:and, s(:true), s(:true))) + end + + specify do + expect(apply).to eql( + [ + s(:and, s(:nil), s(:true)), + s(:and, s(:true), s(:nil)) + ].to_set + ) + end + end +end diff --git a/spec/unit/mutant/mutator/registry_spec.rb b/spec/unit/mutant/mutator/registry_spec.rb index 405a634db..336b9448b 100644 --- a/spec/unit/mutant/mutator/registry_spec.rb +++ b/spec/unit/mutant/mutator/registry_spec.rb @@ -1,11 +1,33 @@ RSpec.describe Mutant::Mutator::Registry do - describe '#lookup' do - subject { Mutant::Mutator::REGISTRY.lookup(node) } + let(:object) { described_class.new } + let(:mutator) { class_double(Mutant::Mutator) } + let(:node) { s(:true) } + let(:expected_arguments) { [node, nil] } + + before do + allow(mutator).to receive(:call).with(*expected_arguments).and_return([s(:nil)]) + end + + describe '#call' do + let(:call_arguments) { [node] } + + subject { object.call(*call_arguments) } + + before do + object.register(:true, mutator) + end + + context 'on parent given' do + let(:call_arguments) { [node, s(:and)] } + let(:expected_arguments) { call_arguments } + + it { should eql([s(:nil)]) } + end context 'on registered node' do let(:node) { s(:true) } - it { should eql(Mutant::Mutator::Node::Literal::Boolean) } + it { should eql([s(:nil)]) } end context 'on unknown node' do @@ -18,26 +40,22 @@ end describe '#register' do - let(:object) { described_class.new } - - let(:mutator) { instance_double(Mutant::Mutator) } - subject { object.register(type, mutator) } context 'when registering an invalid node type' do let(:type) { :invalid } it 'raises error' do - expect { subject }.to raise_error(described_class::RegistryError, 'Invalid type registration: invalid') + expect { subject }.to raise_error(described_class::RegistryError, 'Invalid type registration: :invalid') end end context 'when registering a valid node type' do let(:type) { :true } - it 'allows to lookup mutator' do + it 'allows to call mutator' do subject - expect(object.lookup(s(type))).to be(mutator) + expect(object.call(s(type))).to eql([s(:nil)]) end it_behaves_like 'a command method' @@ -48,7 +66,7 @@ it 'allows to lookup mutator' do object.register(type, mutator) - expect { subject }.to raise_error(described_class::RegistryError, 'Duplicate type registration: true') + expect { subject }.to raise_error(described_class::RegistryError, 'Duplicate type registration: :true') end it_behaves_like 'a command method' diff --git a/spec/unit/mutant/mutator_spec.rb b/spec/unit/mutant/mutator_spec.rb new file mode 100644 index 000000000..3f8a1e074 --- /dev/null +++ b/spec/unit/mutant/mutator_spec.rb @@ -0,0 +1,21 @@ +RSpec.describe Mutant::Mutator do + describe '.handle' do + let(:object) { described_class } + + subject do + Class.new(described_class) do + const_set(:REGISTRY, Mutant::Mutator::Registry.new) + + handle :send + + def dispatch + emit(parent) + end + end + end + + it 'should register mutator' do + expect(subject::REGISTRY.call(s(:send), s(:parent))).to eql([s(:parent)].to_set) + end + end +end diff --git a/spec/unit/mutant/subject_spec.rb b/spec/unit/mutant/subject_spec.rb index d13c6ccb3..fd8ed7ac1 100644 --- a/spec/unit/mutant/subject_spec.rb +++ b/spec/unit/mutant/subject_spec.rb @@ -71,11 +71,11 @@ def foo subject { object.mutations } before do - expect(Mutant::Mutator).to receive(:each).with(node).and_return([mutation_a, mutation_b]) + expect(Mutant::Mutator::REGISTRY).to receive(:call).with(node).and_return([mutation_a, mutation_b]) end - let(:mutation_a) { double('Mutation A') } - let(:mutation_b) { double('Mutation B') } + let(:mutation_a) { instance_double(Parser::AST::Node, :mutation_a) } + let(:mutation_b) { instance_double(Parser::AST::Node, :mutation_b) } it 'generates neutral and evil mutations' do should eql([