mutant 0.10.21 → 0.10.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +32 -13
  3. data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
  4. data/lib/mutant/ast/regexp.rb +54 -0
  5. data/lib/mutant/ast/regexp/transformer.rb +150 -0
  6. data/lib/mutant/ast/regexp/transformer/direct.rb +121 -0
  7. data/lib/mutant/ast/regexp/transformer/named_group.rb +50 -0
  8. data/lib/mutant/ast/regexp/transformer/options_group.rb +68 -0
  9. data/lib/mutant/ast/regexp/transformer/quantifier.rb +92 -0
  10. data/lib/mutant/ast/regexp/transformer/recursive.rb +56 -0
  11. data/lib/mutant/ast/regexp/transformer/root.rb +28 -0
  12. data/lib/mutant/ast/regexp/transformer/text.rb +58 -0
  13. data/lib/mutant/ast/types.rb +115 -2
  14. data/lib/mutant/bootstrap.rb +1 -1
  15. data/lib/mutant/cli/command.rb +4 -0
  16. data/lib/mutant/cli/command/environment.rb +9 -3
  17. data/lib/mutant/cli/command/environment/subject.rb +0 -4
  18. data/lib/mutant/cli/command/environment/test.rb +36 -0
  19. data/lib/mutant/cli/command/root.rb +1 -1
  20. data/lib/mutant/config.rb +9 -55
  21. data/lib/mutant/config/coverage_criteria.rb +61 -0
  22. data/lib/mutant/context.rb +1 -1
  23. data/lib/mutant/env.rb +2 -2
  24. data/lib/mutant/expression.rb +0 -12
  25. data/lib/mutant/expression/method.rb +4 -4
  26. data/lib/mutant/expression/methods.rb +5 -4
  27. data/lib/mutant/expression/namespace.rb +1 -1
  28. data/lib/mutant/integration.rb +8 -2
  29. data/lib/mutant/isolation/fork.rb +4 -11
  30. data/lib/mutant/loader.rb +1 -1
  31. data/lib/mutant/matcher.rb +3 -3
  32. data/lib/mutant/matcher/config.rb +30 -8
  33. data/lib/mutant/matcher/method.rb +10 -9
  34. data/lib/mutant/matcher/method/instance.rb +6 -2
  35. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  36. data/lib/mutant/matcher/methods.rb +2 -4
  37. data/lib/mutant/meta/example.rb +1 -1
  38. data/lib/mutant/meta/example/dsl.rb +6 -1
  39. data/lib/mutant/meta/example/verification.rb +1 -1
  40. data/lib/mutant/mutation.rb +1 -1
  41. data/lib/mutant/mutator.rb +8 -1
  42. data/lib/mutant/mutator/node/argument.rb +2 -2
  43. data/lib/mutant/mutator/node/block.rb +5 -1
  44. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  45. data/lib/mutant/mutator/node/kwargs.rb +44 -0
  46. data/lib/mutant/mutator/node/literal/regex.rb +12 -0
  47. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
  48. data/lib/mutant/mutator/node/regexp.rb +20 -0
  49. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  50. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  51. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  52. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  53. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  54. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  55. data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
  56. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  57. data/lib/mutant/mutator/node/regopt.rb +1 -1
  58. data/lib/mutant/mutator/node/sclass.rb +1 -1
  59. data/lib/mutant/mutator/node/send.rb +55 -6
  60. data/lib/mutant/parallel.rb +2 -2
  61. data/lib/mutant/parallel/driver.rb +1 -1
  62. data/lib/mutant/parallel/worker.rb +1 -1
  63. data/lib/mutant/parser.rb +1 -1
  64. data/lib/mutant/pipe.rb +1 -1
  65. data/lib/mutant/procto.rb +23 -0
  66. data/lib/mutant/reporter/cli/printer.rb +10 -4
  67. data/lib/mutant/reporter/cli/printer/env.rb +3 -3
  68. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
  69. data/lib/mutant/selector.rb +1 -1
  70. data/lib/mutant/subject.rb +2 -4
  71. data/lib/mutant/subject/method/instance.rb +6 -45
  72. data/lib/mutant/timer.rb +2 -2
  73. data/lib/mutant/transform.rb +25 -0
  74. data/lib/mutant/variable.rb +322 -0
  75. data/lib/mutant/version.rb +1 -1
  76. data/lib/mutant/world.rb +2 -3
  77. metadata +39 -151
  78. data/lib/mutant/warnings.rb +0 -106
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # Abstract matcher to find subjects to mutate
5
5
  class Matcher
6
- include Adamantium::Flat, AbstractType
6
+ include Adamantium, AbstractType
7
7
 
8
8
  # Call matcher
9
9
  #
@@ -20,7 +20,7 @@ module Mutant
20
20
  # @return [Matcher]
21
21
  def self.from_config(config)
22
22
  Filter.new(
23
- Chain.new(config.match_expressions.map(&:matcher)),
23
+ Chain.new(config.subjects.map(&:matcher)),
24
24
  method(:allowed_subject?).curry.call(config)
25
25
  )
26
26
  end
@@ -42,7 +42,7 @@ module Mutant
42
42
  #
43
43
  # @return [Boolean]
44
44
  def self.ignore_subject?(config, subject)
45
- config.ignore_expressions.any? do |expression|
45
+ config.ignore.any? do |expression|
46
46
  expression.prefix?(subject.expression)
47
47
  end
48
48
  end
@@ -5,8 +5,8 @@ module Mutant
5
5
  # Subject matcher configuration
6
6
  class Config
7
7
  include Adamantium, Anima.new(
8
- :ignore_expressions,
9
- :match_expressions,
8
+ :ignore,
9
+ :subjects,
10
10
  :start_expressions,
11
11
  :subject_filters
12
12
  )
@@ -16,16 +16,38 @@ module Mutant
16
16
  ATTRIBUTE_FORMAT = '%s: [%s]'
17
17
  ENUM_DELIMITER = ','
18
18
  EMPTY_ATTRIBUTES = 'empty'
19
- PRESENTATIONS = IceNine.deep_freeze(
20
- ignore_expressions: :syntax,
21
- match_expressions: :syntax,
22
- start_expressions: :syntax,
23
- subject_filters: :inspect
24
- )
19
+ PRESENTATIONS = {
20
+ ignore: :syntax,
21
+ start_expressions: :syntax,
22
+ subject_filters: :inspect,
23
+ subjects: :syntax
24
+ }.freeze
25
+
25
26
  private_constant(*constants(false))
26
27
 
27
28
  DEFAULT = new(Hash[anima.attribute_names.map { |name| [name, []] }])
28
29
 
30
+ expression = Transform::Block.capture(:expression) do |input|
31
+ Mutant::Config::DEFAULT.expression_parser.call(input)
32
+ end
33
+
34
+ expression_array = Transform::Array.new(expression)
35
+
36
+ LOADER =
37
+ Transform::Sequence.new(
38
+ [
39
+ Transform::Hash.new(
40
+ optional: [
41
+ Transform::Hash::Key.new('subjects', expression_array),
42
+ Transform::Hash::Key.new('ignore', expression_array)
43
+ ],
44
+ required: []
45
+ ),
46
+ Transform::Hash::Symbolize.new,
47
+ ->(attributes) { Either::Right.new(DEFAULT.with(attributes)) }
48
+ ]
49
+ )
50
+
29
51
  # Inspection string
30
52
  #
31
53
  # @return [String]
@@ -5,7 +5,7 @@ module Mutant
5
5
  # Abstract base class for method matchers
6
6
  class Method < self
7
7
  include AbstractType,
8
- Adamantium::Flat,
8
+ Adamantium,
9
9
  Concord::Public.new(:scope, :target_method, :evaluator)
10
10
 
11
11
  # Source locations we cannot acces
@@ -32,11 +32,13 @@ module Mutant
32
32
  # logic would be implemented directly on the Matcher::Method
33
33
  # instance
34
34
  class Evaluator
35
- include AbstractType,
36
- Adamantium,
37
- Concord.new(:scope, :target_method, :env),
38
- Procto.call,
39
- AST::NodePredicates
35
+ include(
36
+ AbstractType,
37
+ Adamantium,
38
+ Concord.new(:scope, :target_method, :env),
39
+ Procto,
40
+ AST::NodePredicates
41
+ )
40
42
 
41
43
  # Matched subjects
42
44
  #
@@ -87,9 +89,8 @@ module Mutant
87
89
  node = matched_node_path.last || return
88
90
 
89
91
  self.class::SUBJECT_CLASS.new(
90
- context: context,
91
- node: node,
92
- warnings: env.world.warnings
92
+ context: context,
93
+ node: node
93
94
  )
94
95
  end
95
96
  memoize :subject
@@ -13,9 +13,8 @@ module Mutant
13
13
  #
14
14
  # @return [Matcher::Method::Instance]
15
15
  def self.new(scope, target_method)
16
- name = target_method.name
17
16
  evaluator =
18
- if scope.respond_to?(:memoized?) && scope.memoized?(name)
17
+ if memoized_method?(scope, target_method.name)
19
18
  Evaluator::Memoized
20
19
  else
21
20
  Evaluator
@@ -24,6 +23,11 @@ module Mutant
24
23
  super(scope, target_method, evaluator)
25
24
  end
26
25
 
26
+ def self.memoized_method?(scope, method_name)
27
+ scope < Adamantium && scope.memoized?(method_name)
28
+ end
29
+ private_class_method :memoized_method?
30
+
27
31
  # Instance method specific evaluator
28
32
  class Evaluator < Evaluator
29
33
  SUBJECT_CLASS = Subject::Method::Instance
@@ -80,7 +80,7 @@ module Mutant
80
80
  end # Evaluator
81
81
 
82
82
  private_constant(*constants(false))
83
- end # Singleton
83
+ end # Metaclass
84
84
  end # Method
85
85
  end # Matcher
86
86
  end # Mutant
@@ -6,11 +6,11 @@ module Mutant
6
6
  class Methods < self
7
7
  include AbstractType, Concord.new(:scope)
8
8
 
9
- CANDIDATE_NAMES = IceNine.deep_freeze(%i[
9
+ CANDIDATE_NAMES = %i[
10
10
  public_instance_methods
11
11
  private_instance_methods
12
12
  protected_instance_methods
13
- ])
13
+ ].freeze
14
14
 
15
15
  private_constant(*constants(false))
16
16
 
@@ -62,7 +62,6 @@ module Mutant
62
62
  def candidate_scope
63
63
  scope.singleton_class
64
64
  end
65
- memoize :candidate_scope, freezer: :noop
66
65
 
67
66
  end # Singleton
68
67
 
@@ -79,7 +78,6 @@ module Mutant
79
78
  def candidate_scope
80
79
  scope.singleton_class
81
80
  end
82
- memoize :candidate_scope, freezer: :noop
83
81
  end # Metaclass
84
82
 
85
83
  # Matcher for instance methods
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  module Meta
5
5
  class Example
6
- include Adamantium::Flat
6
+ include Adamantium
7
7
 
8
8
  include Anima.new(
9
9
  :expected,
@@ -37,7 +37,7 @@ module Mutant
37
37
  # @return [Example]
38
38
  #
39
39
  # @raise [RuntimeError]
40
- # in case example cannot be build
40
+ # in case the example cannot be built
41
41
  def example
42
42
  fail 'source not defined' unless @source
43
43
 
@@ -82,6 +82,11 @@ module Mutant
82
82
  mutation('self')
83
83
  end
84
84
 
85
+ def regexp_mutations
86
+ mutation('//')
87
+ mutation('/nomatch\A/')
88
+ end
89
+
85
90
  def node(input)
86
91
  case input
87
92
  when String
@@ -5,7 +5,7 @@ module Mutant
5
5
  class Example
6
6
  # Example verification
7
7
  class Verification
8
- include Adamantium::Flat, Concord.new(:example, :mutations)
8
+ include Adamantium, Concord.new(:example, :mutations)
9
9
 
10
10
  # Test if mutation was verified successfully
11
11
  #
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # Represent a mutated node with its subject
5
5
  class Mutation
6
- include AbstractType, Adamantium::Flat
6
+ include AbstractType, Adamantium
7
7
  include Concord::Public.new(:subject, :node)
8
8
 
9
9
  CODE_DELIMITER = "\0"
@@ -6,7 +6,12 @@ module Mutant
6
6
 
7
7
  REGISTRY = Registry.new
8
8
 
9
- include Adamantium::Flat, Concord.new(:input, :parent), AbstractType, Procto.call(:output)
9
+ include(
10
+ Adamantium,
11
+ Concord.new(:input, :parent),
12
+ AbstractType,
13
+ Procto
14
+ )
10
15
 
11
16
  # Lookup and invoke dedicated AST mutator
12
17
  #
@@ -30,6 +35,8 @@ module Mutant
30
35
  # @return [Set<Parser::AST::Node>]
31
36
  attr_reader :output
32
37
 
38
+ alias_method :call, :output
39
+
33
40
  private
34
41
 
35
42
  def initialize(_input, _parent = nil)
@@ -30,10 +30,10 @@ module Mutant
30
30
  # Mutator for optional arguments
31
31
  class Optional < self
32
32
 
33
- TYPE_MAP = IceNine.deep_freeze(
33
+ TYPE_MAP = {
34
34
  optarg: :arg,
35
35
  kwoptarg: :kwarg
36
- )
36
+ }.freeze
37
37
 
38
38
  handle(:optarg, :kwoptarg)
39
39
 
@@ -21,7 +21,7 @@ module Mutant
21
21
  end
22
22
 
23
23
  def mutate_body
24
- emit_body(nil)
24
+ emit_body(nil) unless unconditional_loop?
25
25
  emit_body(N_RAISE)
26
26
 
27
27
  return unless body
@@ -31,6 +31,10 @@ module Mutant
31
31
  mutate_body_receiver
32
32
  end
33
33
 
34
+ def unconditional_loop?
35
+ send.eql?(s(:send, nil, :loop))
36
+ end
37
+
34
38
  def body_has_control?
35
39
  AST.find_last_path(body) do |node|
36
40
  n_break?(node) || n_next?(node)
@@ -18,7 +18,7 @@ module Mutant
18
18
  end
19
19
  end
20
20
 
21
- end # Dstr
21
+ end # DynamicLiteral
22
22
  end # Node
23
23
  end # Mutator
24
24
  end # Mutant
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ # Mutator for kwargs node
7
+ class Kwargs < self
8
+
9
+ DISALLOW = %i[nil self].freeze
10
+
11
+ private_constant(*constants(false))
12
+
13
+ handle(:kwargs)
14
+
15
+ private
16
+
17
+ def dispatch
18
+ emit_argument_presence
19
+ emit_argument_mutations
20
+ end
21
+
22
+ def emit_argument_presence
23
+ Util::Array::Presence.call(children).each do |children|
24
+ emit_type(*children) unless children.empty?
25
+ end
26
+ end
27
+
28
+ def emit_argument_mutations
29
+ children.each_with_index do |child, index|
30
+ Mutator.mutate(child).each do |mutant|
31
+ unless forbid_argument?(mutant)
32
+ emit_child_update(index, mutant)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def forbid_argument?(node)
39
+ n_pair?(node) && DISALLOW.include?(node.children.first.type)
40
+ end
41
+ end # Kwargs
42
+ end # Node
43
+ end # Mutator
44
+ end # Mutant
@@ -19,6 +19,7 @@ module Mutant
19
19
  end
20
20
 
21
21
  def dispatch
22
+ mutate_body
22
23
  emit_singletons unless parent_node
23
24
  children.each_with_index do |child, index|
24
25
  mutate_child(index) unless n_str?(child)
@@ -27,6 +28,17 @@ module Mutant
27
28
  emit_type(s(:str, NULL_REGEXP_SOURCE), options)
28
29
  end
29
30
 
31
+ def mutate_body
32
+ # NOTE: will only mutate parts of regexp body if the body is composed of only strings.
33
+ # Regular expressions with interpolation are skipped.
34
+ return unless (body_ast = AST::Regexp.expand_regexp_ast(input))
35
+
36
+ Mutator.mutate(body_ast).each do |mutation|
37
+ source = AST::Regexp.to_expression(mutation).to_s
38
+ emit_type(s(:str, source), options)
39
+ end
40
+ end
41
+
30
42
  end # Regex
31
43
  end # Literal
32
44
  end # Node
@@ -17,9 +17,9 @@ module Mutant
17
17
  lvasgn: EMPTY_STRING
18
18
  }
19
19
 
20
- MAP = IceNine.deep_freeze(
21
- map.transform_values { |prefix| [prefix, /^#{::Regexp.escape(prefix)}/] }
22
- )
20
+ MAP = map
21
+ .transform_values { |prefix| [prefix, /^#{::Regexp.escape(prefix)}/] }
22
+ .freeze
23
23
 
24
24
  handle(*MAP.keys)
25
25
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ module Regexp
7
+ # Mutator for root expression regexp wrapper
8
+ class RootExpression < Node
9
+ handle(:regexp_root_expression)
10
+
11
+ private
12
+
13
+ def dispatch
14
+ children.each_index(&method(:mutate_child))
15
+ end
16
+ end # RootExpression
17
+ end # Regexp
18
+ end # Node
19
+ end # Mutator
20
+ end # Mutant
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ module Regexp
7
+ # Mutator for pipe in `/foo|bar/` regexp
8
+ class AlternationMeta < Node
9
+ handle(:regexp_alternation_meta)
10
+
11
+ private
12
+
13
+ def dispatch
14
+ children.each_index(&method(:delete_child))
15
+ end
16
+ end # AlternationMeta
17
+ end # Regexp
18
+ end # Node
19
+ end # Mutator
20
+ end # Mutant
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ module Regexp
7
+ # Mutator for beginning of line anchor `^`
8
+ class BeginningOfLineAnchor < Node
9
+ handle(:regexp_bol_anchor)
10
+
11
+ private
12
+
13
+ def dispatch
14
+ emit(s(:regexp_bos_anchor))
15
+ end
16
+ end # BeginningOfLineAnchor
17
+ end # Regexp
18
+ end # Node
19
+ end # Mutator
20
+ end # Mutant