mutant 0.10.23 → 0.10.28

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +34 -14
  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/integration.rb +8 -2
  28. data/lib/mutant/isolation/exception.rb +22 -0
  29. data/lib/mutant/isolation/fork.rb +9 -12
  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 +14 -13
  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 -2
  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.rb +0 -5
  43. data/lib/mutant/mutator/node/argument.rb +2 -2
  44. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  45. data/lib/mutant/mutator/node/index.rb +1 -0
  46. data/lib/mutant/mutator/node/kwargs.rb +2 -2
  47. data/lib/mutant/mutator/node/literal/float.rb +1 -3
  48. data/lib/mutant/mutator/node/literal/integer.rb +3 -6
  49. data/lib/mutant/mutator/node/literal/regex.rb +12 -0
  50. data/lib/mutant/mutator/node/module.rb +19 -0
  51. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
  52. data/lib/mutant/mutator/node/regexp.rb +20 -0
  53. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  54. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  55. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  56. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  57. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  58. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  59. data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
  60. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  61. data/lib/mutant/mutator/node/regopt.rb +1 -1
  62. data/lib/mutant/mutator/node/sclass.rb +1 -1
  63. data/lib/mutant/mutator/node/send.rb +73 -6
  64. data/lib/mutant/parallel.rb +2 -2
  65. data/lib/mutant/parallel/driver.rb +1 -1
  66. data/lib/mutant/parallel/worker.rb +1 -1
  67. data/lib/mutant/parser.rb +1 -1
  68. data/lib/mutant/pipe.rb +1 -1
  69. data/lib/mutant/procto.rb +23 -0
  70. data/lib/mutant/reporter/cli/printer.rb +10 -4
  71. data/lib/mutant/reporter/cli/printer/env.rb +3 -3
  72. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
  73. data/lib/mutant/reporter/cli/printer/isolation_result.rb +3 -1
  74. data/lib/mutant/selector.rb +1 -1
  75. data/lib/mutant/subject.rb +2 -4
  76. data/lib/mutant/subject/method/instance.rb +6 -45
  77. data/lib/mutant/transform.rb +25 -0
  78. data/lib/mutant/variable.rb +322 -0
  79. data/lib/mutant/version.rb +1 -1
  80. data/lib/mutant/world.rb +2 -3
  81. metadata +38 -149
  82. data/lib/mutant/warnings.rb +0 -106
data/lib/mutant/loader.rb CHANGED
@@ -18,7 +18,7 @@ module Mutant
18
18
 
19
19
  # Vale returned on MRI detecting void value expressions
20
20
  class VoidValue < self
21
- end # voidValue
21
+ end # VoidValue
22
22
  end # Result
23
23
 
24
24
  # Call loader
@@ -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,12 +5,9 @@ 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
- # Source locations we cannot acces
12
- BLACKLIST = %w[(eval) <internal:prelude>].to_set.freeze
13
-
14
11
  SOURCE_LOCATION_WARNING_FORMAT =
15
12
  '%s does not have a valid source location, unable to emit subject'
16
13
 
@@ -32,11 +29,13 @@ module Mutant
32
29
  # logic would be implemented directly on the Matcher::Method
33
30
  # instance
34
31
  class Evaluator
35
- include AbstractType,
36
- Adamantium,
37
- Concord.new(:scope, :target_method, :env),
38
- Procto.call,
39
- AST::NodePredicates
32
+ include(
33
+ AbstractType,
34
+ Adamantium,
35
+ Concord.new(:scope, :target_method, :env),
36
+ Procto,
37
+ AST::NodePredicates
38
+ )
40
39
 
41
40
  # Matched subjects
42
41
  #
@@ -51,7 +50,10 @@ module Mutant
51
50
 
52
51
  def skip?
53
52
  location = source_location
54
- if location.nil? || BLACKLIST.include?(location.first)
53
+
54
+ file = location&.first
55
+
56
+ if location.nil? || !file.end_with?('.rb')
55
57
  env.warn(SOURCE_LOCATION_WARNING_FORMAT % target_method)
56
58
  elsif matched_node_path.any?(&method(:n_block?))
57
59
  env.warn(CLOSURE_WARNING_FORMAT % target_method)
@@ -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
 
@@ -79,7 +79,11 @@ module Mutant
79
79
 
80
80
  def singleton_mutations
81
81
  mutation('nil')
82
- mutation('self')
82
+ end
83
+
84
+ def regexp_mutations
85
+ mutation('//')
86
+ mutation('/nomatch\A/')
83
87
  end
84
88
 
85
89
  def node(input)
@@ -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)
@@ -67,11 +67,6 @@ module Mutant
67
67
 
68
68
  def emit_singletons
69
69
  emit_nil
70
- emit_self
71
- end
72
-
73
- def emit_self
74
- emit(N_SELF)
75
70
  end
76
71
 
77
72
  def emit_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
 
@@ -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
@@ -17,6 +17,7 @@ module Mutant
17
17
  def dispatch
18
18
  emit_singletons
19
19
  emit_receiver_mutations { |node| !n_nil?(node) }
20
+ emit_type(N_SELF, *children.drop(1))
20
21
  emit(receiver)
21
22
  emit_send_forms
22
23
  emit_drop_mutation
@@ -28,7 +28,7 @@ module Mutant
28
28
  def emit_argument_mutations
29
29
  children.each_with_index do |child, index|
30
30
  Mutator.mutate(child).each do |mutant|
31
- if forbid_argument?(mutant)
31
+ unless forbid_argument?(mutant)
32
32
  emit_child_update(index, mutant)
33
33
  end
34
34
  end
@@ -36,7 +36,7 @@ module Mutant
36
36
  end
37
37
 
38
38
  def forbid_argument?(node)
39
- !(n_pair?(node) && DISALLOW.include?(node.children.first.type))
39
+ n_pair?(node) && DISALLOW.include?(node.children.first.type)
40
40
  end
41
41
  end # Kwargs
42
42
  end # Node
@@ -28,9 +28,7 @@ module Mutant
28
28
  end
29
29
 
30
30
  def values
31
- original = children.first
32
-
33
- [0.0, 1.0, -original]
31
+ [0.0, 1.0]
34
32
  end
35
33
 
36
34
  end # Float
@@ -9,6 +9,8 @@ module Mutant
9
9
 
10
10
  handle(:int)
11
11
 
12
+ children :value
13
+
12
14
  private
13
15
 
14
16
  def dispatch
@@ -17,12 +19,7 @@ module Mutant
17
19
  end
18
20
 
19
21
  def values
20
- [0, 1, -value, value + 1, value - 1]
21
- end
22
-
23
- def value
24
- value, = children
25
- value
22
+ [0, 1, value + 1, value - 1]
26
23
  end
27
24
 
28
25
  end # Integer
@@ -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
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ class Module < self
7
+ handle :module
8
+
9
+ children :klass, :body
10
+
11
+ private
12
+
13
+ def dispatch
14
+ emit_body_mutations if body
15
+ end
16
+ end # Module
17
+ end # Node
18
+ end # Mutator
19
+ end # Mutant
@@ -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