mutant 0.10.25 → 0.10.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mutant +1 -1
  3. data/lib/mutant.rb +15 -10
  4. data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
  5. data/lib/mutant/ast/regexp.rb +17 -0
  6. data/lib/mutant/ast/regexp/transformer.rb +2 -2
  7. data/lib/mutant/ast/regexp/transformer/quantifier.rb +4 -2
  8. data/lib/mutant/bootstrap.rb +14 -6
  9. data/lib/mutant/cli/command.rb +4 -0
  10. data/lib/mutant/cli/command/environment/subject.rb +0 -4
  11. data/lib/mutant/cli/command/environment/test.rb +36 -0
  12. data/lib/mutant/cli/command/root.rb +1 -1
  13. data/lib/mutant/config.rb +18 -6
  14. data/lib/mutant/context.rb +1 -1
  15. data/lib/mutant/env.rb +6 -2
  16. data/lib/mutant/expression.rb +1 -1
  17. data/lib/mutant/expression/method.rb +4 -4
  18. data/lib/mutant/expression/methods.rb +5 -4
  19. data/lib/mutant/hooks.rb +77 -0
  20. data/lib/mutant/integration.rb +8 -2
  21. data/lib/mutant/isolation/exception.rb +22 -0
  22. data/lib/mutant/isolation/fork.rb +9 -12
  23. data/lib/mutant/matcher.rb +1 -1
  24. data/lib/mutant/matcher/config.rb +4 -3
  25. data/lib/mutant/matcher/method.rb +12 -10
  26. data/lib/mutant/matcher/method/instance.rb +6 -2
  27. data/lib/mutant/matcher/methods.rb +3 -5
  28. data/lib/mutant/meta/example.rb +1 -1
  29. data/lib/mutant/meta/example/dsl.rb +0 -1
  30. data/lib/mutant/meta/example/verification.rb +1 -1
  31. data/lib/mutant/mutation.rb +1 -1
  32. data/lib/mutant/mutator.rb +8 -1
  33. data/lib/mutant/mutator/node.rb +0 -5
  34. data/lib/mutant/mutator/node/argument.rb +2 -2
  35. data/lib/mutant/mutator/node/index.rb +1 -0
  36. data/lib/mutant/mutator/node/literal/float.rb +1 -3
  37. data/lib/mutant/mutator/node/literal/integer.rb +3 -6
  38. data/lib/mutant/mutator/node/literal/range.rb +1 -1
  39. data/lib/mutant/mutator/node/literal/regex.rb +3 -17
  40. data/lib/mutant/mutator/node/module.rb +19 -0
  41. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
  42. data/lib/mutant/mutator/node/regexp/character_type.rb +1 -1
  43. data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
  44. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +2 -2
  45. data/lib/mutant/mutator/node/regopt.rb +1 -1
  46. data/lib/mutant/mutator/node/send.rb +63 -7
  47. data/lib/mutant/parallel.rb +2 -2
  48. data/lib/mutant/parallel/driver.rb +1 -1
  49. data/lib/mutant/parallel/worker.rb +4 -1
  50. data/lib/mutant/parser.rb +1 -1
  51. data/lib/mutant/pipe.rb +13 -1
  52. data/lib/mutant/procto.rb +23 -0
  53. data/lib/mutant/reporter/cli/printer.rb +10 -4
  54. data/lib/mutant/reporter/cli/printer/env.rb +2 -2
  55. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
  56. data/lib/mutant/reporter/cli/printer/isolation_result.rb +3 -1
  57. data/lib/mutant/repository/diff.rb +1 -1
  58. data/lib/mutant/selector.rb +1 -1
  59. data/lib/mutant/subject.rb +1 -1
  60. data/lib/mutant/subject/method/instance.rb +5 -42
  61. data/lib/mutant/transform.rb +1 -1
  62. data/lib/mutant/variable.rb +322 -0
  63. data/lib/mutant/version.rb +1 -1
  64. data/lib/mutant/world.rb +1 -1
  65. metadata +12 -159
@@ -21,7 +21,7 @@ module Mutant
21
21
 
22
22
  def dispatch
23
23
  emit_singletons
24
- emit_lower_bound_mutations
24
+ emit_lower_bound_mutations if lower_bound
25
25
 
26
26
  return unless upper_bound
27
27
 
@@ -28,11 +28,10 @@ module Mutant
28
28
  emit_type(s(:str, NULL_REGEXP_SOURCE), options)
29
29
  end
30
30
 
31
- # NOTE: will only mutate parts of regexp body if the
32
- # body is composed of only strings. Regular expressions
33
- # with interpolation are skipped
34
31
  def mutate_body
35
- return unless body.all?(&method(:n_str?))
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))
36
35
 
37
36
  Mutator.mutate(body_ast).each do |mutation|
38
37
  source = AST::Regexp.to_expression(mutation).to_s
@@ -40,19 +39,6 @@ module Mutant
40
39
  end
41
40
  end
42
41
 
43
- def body_ast
44
- AST::Regexp.to_ast(body_expression)
45
- end
46
-
47
- def body_expression
48
- AST::Regexp.parse(body.map(&:children).join)
49
- end
50
- memoize :body_expression
51
-
52
- def body
53
- children.slice(0...-1)
54
- end
55
-
56
42
  end # Regex
57
43
  end # Literal
58
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
 
@@ -15,7 +15,7 @@ module Mutant
15
15
  regexp_xgrapheme_type: :regexp_linebreak_type
16
16
  }
17
17
 
18
- MAP = IceNine.deep_freeze(map.merge(map.invert))
18
+ MAP = map.merge(map.invert)
19
19
 
20
20
  handle(*MAP.keys)
21
21
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ module Regexp
7
+ # Mutator for regexp named capture groups, such as `/(?<foo>bar)/`
8
+ class NamedGroup < Node
9
+ handle(:regexp_named_group)
10
+
11
+ children :name, :group
12
+
13
+ private
14
+
15
+ def dispatch
16
+ return unless group
17
+
18
+ emit_group_mutations
19
+
20
+ # Allows unused captures to be kept and named if they are explicitly prefixed with an
21
+ # underscore, like we allow with unused local variables.
22
+ return if name_underscored?
23
+
24
+ emit(s(:regexp_passive_group, group))
25
+ emit_name_underscore_mutation
26
+ end
27
+
28
+ def emit_name_underscore_mutation
29
+ emit_type("_#{name}", group)
30
+ end
31
+
32
+ def name_underscored?
33
+ name.start_with?('_')
34
+ end
35
+ end # EndOfLineAnchor
36
+ end # Regexp
37
+ end # Node
38
+ end # Mutator
39
+ end # Mutant
@@ -6,11 +6,11 @@ module Mutant
6
6
  module Regexp
7
7
  # Mutator for zero-or-more quantifier, `*`
8
8
  class ZeroOrMore < Node
9
- MAP = IceNine.deep_freeze(
9
+ MAP = {
10
10
  regexp_greedy_zero_or_more: :regexp_greedy_one_or_more,
11
11
  regexp_reluctant_zero_or_more: :regexp_reluctant_one_or_more,
12
12
  regexp_possessive_zero_or_more: :regexp_possessive_one_or_more
13
- )
13
+ }.freeze
14
14
 
15
15
  handle(*MAP.keys)
16
16
 
@@ -7,7 +7,7 @@ module Mutant
7
7
  # Regular expression options mutation
8
8
  class Regopt < self
9
9
 
10
- MUTATED_FLAGS = IceNine.deep_freeze(%i[i])
10
+ MUTATED_FLAGS = %i[i].freeze
11
11
 
12
12
  handle(:regopt)
13
13
 
@@ -13,7 +13,7 @@ module Mutant
13
13
 
14
14
  children :receiver, :selector
15
15
 
16
- SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
16
+ SELECTOR_REPLACEMENTS = {
17
17
  :< => %i[== eql? equal?],
18
18
  :<= => %i[< == eql? equal?],
19
19
  :== => %i[eql? equal?],
@@ -25,15 +25,14 @@ module Mutant
25
25
  all?: %i[any?],
26
26
  any?: %i[all?],
27
27
  at: %i[fetch key?],
28
- eql?: %i[equal?],
29
28
  fetch: %i[key?],
30
29
  flat_map: %i[map],
31
30
  gsub: %i[sub],
32
31
  is_a?: %i[instance_of?],
33
32
  kind_of?: %i[instance_of?],
34
33
  map: %i[each],
35
- method: %i[public_method],
36
34
  match: %i[match?],
35
+ method: %i[public_method],
37
36
  reverse_each: %i[each],
38
37
  reverse_map: %i[map each],
39
38
  reverse_merge: %i[merge],
@@ -43,13 +42,17 @@ module Mutant
43
42
  to_i: %i[to_int],
44
43
  to_s: %i[to_str],
45
44
  values_at: %i[fetch_values]
46
- )
45
+ }.freeze.tap { |hash| hash.values(&:freeze) }
47
46
 
48
- RECEIVER_SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
47
+ RECEIVER_SELECTOR_REPLACEMENTS = {
49
48
  Date: {
50
49
  parse: %i[jd civil strptime iso8601 rfc3339 xmlschema rfc2822 rfc822 httpdate jisx0301]
51
- }
52
- )
50
+ }.freeze
51
+ }.freeze
52
+
53
+ REGEXP_MATCH_METHODS = %i[=~ match match?].freeze
54
+ REGEXP_START_WITH_NODES = %i[regexp_bos_anchor regexp_literal_literal].freeze
55
+ REGEXP_END_WITH_NODES = %i[regexp_literal_literal regexp_eos_anchor].freeze
53
56
 
54
57
  private
55
58
 
@@ -84,6 +87,8 @@ module Mutant
84
87
  end
85
88
 
86
89
  def emit_selector_specific_mutations
90
+ emit_reduce_to_sum_mutation
91
+ emit_start_end_with_mutations
87
92
  emit_predicate_mutations
88
93
  emit_array_mutation
89
94
  emit_static_send
@@ -94,6 +99,50 @@ module Mutant
94
99
  emit_lambda_mutation
95
100
  end
96
101
 
102
+ def emit_reduce_to_sum_mutation
103
+ return unless selector.equal?(:reduce)
104
+
105
+ reducer = arguments.last
106
+
107
+ return unless reducer.eql?(s(:sym, :+)) || reducer.eql?(s(:block_pass, s(:sym, :+)))
108
+
109
+ if arguments.length > 1
110
+ initial_value = arguments.first
111
+ emit_type(receiver, :sum, initial_value)
112
+ else
113
+ emit_type(receiver, :sum)
114
+ end
115
+ end
116
+
117
+ def emit_start_end_with_mutations # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
118
+ return unless REGEXP_MATCH_METHODS.include?(selector) && arguments.one?
119
+
120
+ argument = Mutant::Util.one(arguments)
121
+
122
+ return unless argument.type.equal?(:regexp) && (
123
+ regexp_ast = AST::Regexp.expand_regexp_ast(argument)
124
+ )
125
+
126
+ regexp_children = regexp_ast.children
127
+
128
+ case regexp_children.map(&:type)
129
+ when REGEXP_START_WITH_NODES
130
+ emit_start_with(regexp_children)
131
+ when REGEXP_END_WITH_NODES
132
+ emit_end_with(regexp_children)
133
+ end
134
+ end
135
+
136
+ def emit_start_with(regexp_nodes)
137
+ literal = Mutant::Util.one(regexp_nodes.last.children)
138
+ emit_type(receiver, :start_with?, s(:str, literal))
139
+ end
140
+
141
+ def emit_end_with(regexp_nodes)
142
+ literal = Mutant::Util.one(regexp_nodes.first.children)
143
+ emit_type(receiver, :end_with?, s(:str, literal))
144
+ end
145
+
97
146
  def emit_predicate_mutations
98
147
  return unless selector.match?(/\?\z/) && !selector.equal?(:defined?)
99
148
 
@@ -198,11 +247,18 @@ module Mutant
198
247
  def mutate_receiver
199
248
  return unless receiver
200
249
  emit_implicit_self
250
+ emit_explicit_self
201
251
  emit_receiver_mutations do |node|
202
252
  !n_nil?(node)
203
253
  end
204
254
  end
205
255
 
256
+ def emit_explicit_self
257
+ return if UNARY_METHOD_OPERATORS.include?(selector)
258
+
259
+ emit_receiver(N_SELF) unless n_nil?(receiver)
260
+ end
261
+
206
262
  def emit_implicit_self
207
263
  emit_receiver(nil) if n_self?(receiver) && !(
208
264
  KEYWORDS.include?(selector) ||
@@ -90,7 +90,7 @@ module Mutant
90
90
 
91
91
  # Parallel run configuration
92
92
  class Config
93
- include Adamantium::Flat, Anima.new(
93
+ include Adamantium, Anima.new(
94
94
  :block,
95
95
  :jobs,
96
96
  :process_name,
@@ -102,7 +102,7 @@ module Mutant
102
102
 
103
103
  # Parallel execution status
104
104
  class Status
105
- include Adamantium::Flat, Anima.new(
105
+ include Adamantium, Anima.new(
106
106
  :active_jobs,
107
107
  :done,
108
108
  :payload
@@ -4,7 +4,7 @@ module Mutant
4
4
  module Parallel
5
5
  # Driver for parallelized execution
6
6
  class Driver
7
- include Adamantium::Flat, Anima.new(
7
+ include Adamantium, Anima.new(
8
8
  :threads,
9
9
  :var_active_jobs,
10
10
  :var_final,
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  module Parallel
5
5
  class Worker
6
- include Adamantium::Flat, Anima.new(
6
+ include Adamantium, Anima.new(
7
7
  :connection,
8
8
  :index,
9
9
  :pid,
@@ -29,6 +29,9 @@ module Mutant
29
29
  response = Pipe.from_io(io)
30
30
 
31
31
  pid = process.fork do
32
+ request.reset_binmode
33
+ response.reset_binmode
34
+
32
35
  world.thread.current.name = process_name
33
36
  world.process.setproctitle(process_name)
34
37
 
data/lib/mutant/parser.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # An AST Parser
5
5
  class Parser
6
- include Adamantium::Mutable, Equalizer.new
6
+ include Adamantium, Equalizer.new
7
7
 
8
8
  # Initialize object
9
9
  #
data/lib/mutant/pipe.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # Pipe abstraction
5
5
  class Pipe
6
- include Adamantium::Flat, Anima.new(:reader, :writer)
6
+ include Adamantium, Anima.new(:reader, :writer)
7
7
 
8
8
  # Run block with pipe in binmode
9
9
  #
@@ -35,6 +35,18 @@ module Mutant
35
35
  reader
36
36
  end
37
37
 
38
+ # Set binmode (again)
39
+ #
40
+ # Ruby has a bug where the binmode setting may be lost duringa fork.
41
+ # This API allows to set the binmode again.
42
+ #
43
+ # @return [self]
44
+ def reset_binmode
45
+ reader.binmode
46
+ writer.binmode
47
+ self
48
+ end
49
+
38
50
  class Connection
39
51
  include Anima.new(:marshal, :reader, :writer)
40
52
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Procto
5
+ # Define the .call method on +host+
6
+ #
7
+ # @param [Object] host
8
+ # the hosting object
9
+ #
10
+ # @return [undefined]
11
+ #
12
+ # @api private
13
+ def self.included(host)
14
+ host.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ def call(*arguments)
19
+ new(*arguments).call
20
+ end
21
+ end
22
+ end # Procto
23
+ end # Unparser
@@ -5,13 +5,19 @@ module Mutant
5
5
  class CLI
6
6
  # CLI runner status printer base class
7
7
  class Printer
8
- include AbstractType,
9
- Adamantium::Flat,
10
- Concord.new(:output, :object),
11
- Procto.call(:run)
8
+ include(
9
+ AbstractType,
10
+ Adamantium,
11
+ Concord.new(:output, :object),
12
+ Procto
13
+ )
12
14
 
13
15
  private_class_method :new
14
16
 
17
+ def call
18
+ run
19
+ end
20
+
15
21
  # Create delegators to object
16
22
  #
17
23
  # @return [undefined]
@@ -15,13 +15,13 @@ module Mutant
15
15
  :test_subject_ratio
16
16
  )
17
17
 
18
- FORMATS = IceNine.deep_freeze([
18
+ FORMATS = [
19
19
  [:info, 'Subjects: %s', :amount_subjects ],
20
20
  [:info, 'Total-Tests: %s', :amount_total_tests ],
21
21
  [:info, 'Selected-Tests: %s', :amount_selected_tests],
22
22
  [:info, 'Tests/Subject: %0.2f avg', :test_subject_ratio ],
23
23
  [:info, 'Mutations: %s', :amount_mutations ]
24
- ])
24
+ ].each(&:freeze)
25
25
 
26
26
  # Run printer
27
27
  #
@@ -18,7 +18,7 @@ module Mutant
18
18
  :runtime
19
19
  )
20
20
 
21
- FORMATS = IceNine.deep_freeze([
21
+ FORMATS = [
22
22
  [:info, 'Results: %s', :amount_mutation_results],
23
23
  [:info, 'Kills: %s', :amount_mutations_killed],
24
24
  [:info, 'Alive: %s', :amount_mutations_alive ],
@@ -28,7 +28,7 @@ module Mutant
28
28
  [:info, 'Overhead: %0.2f%%', :overhead_percent ],
29
29
  [:info, 'Mutations/s: %0.2f', :mutations_per_second ],
30
30
  [:status, 'Coverage: %0.2f%%', :coverage_percent ]
31
- ])
31
+ ].each(&:freeze)
32
32
 
33
33
  # Run printer
34
34
  #