mutant 0.10.20 → 0.10.25

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +23 -4
  3. data/lib/mutant/ast/meta/send.rb +0 -1
  4. data/lib/mutant/ast/regexp.rb +37 -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 +90 -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 -11
  14. data/lib/mutant/cli/command/environment.rb +9 -3
  15. data/lib/mutant/config.rb +8 -54
  16. data/lib/mutant/config/coverage_criteria.rb +61 -0
  17. data/lib/mutant/env.rb +8 -6
  18. data/lib/mutant/expression.rb +0 -12
  19. data/lib/mutant/expression/namespace.rb +1 -1
  20. data/lib/mutant/isolation/fork.rb +1 -1
  21. data/lib/mutant/loader.rb +1 -1
  22. data/lib/mutant/matcher.rb +2 -2
  23. data/lib/mutant/matcher/config.rb +27 -6
  24. data/lib/mutant/matcher/method.rb +2 -3
  25. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  26. data/lib/mutant/meta/example/dsl.rb +6 -1
  27. data/lib/mutant/mutator/node/arguments.rb +0 -2
  28. data/lib/mutant/mutator/node/block.rb +5 -1
  29. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  30. data/lib/mutant/mutator/node/kwargs.rb +44 -0
  31. data/lib/mutant/mutator/node/literal/regex.rb +26 -0
  32. data/lib/mutant/mutator/node/literal/symbol.rb +0 -2
  33. data/lib/mutant/mutator/node/regexp.rb +20 -0
  34. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  35. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  36. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  37. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  38. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  39. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  40. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  41. data/lib/mutant/mutator/node/sclass.rb +1 -1
  42. data/lib/mutant/mutator/node/send.rb +36 -19
  43. data/lib/mutant/parallel.rb +43 -28
  44. data/lib/mutant/parallel/driver.rb +9 -3
  45. data/lib/mutant/parallel/worker.rb +60 -2
  46. data/lib/mutant/pipe.rb +94 -0
  47. data/lib/mutant/reporter/cli/printer/env.rb +1 -1
  48. data/lib/mutant/reporter/cli/printer/isolation_result.rb +1 -6
  49. data/lib/mutant/result.rb +8 -0
  50. data/lib/mutant/runner.rb +7 -10
  51. data/lib/mutant/runner/sink.rb +12 -2
  52. data/lib/mutant/subject.rb +1 -3
  53. data/lib/mutant/subject/method/instance.rb +2 -4
  54. data/lib/mutant/timer.rb +2 -4
  55. data/lib/mutant/transform.rb +25 -2
  56. data/lib/mutant/version.rb +1 -1
  57. data/lib/mutant/world.rb +1 -2
  58. metadata +48 -9
  59. data/lib/mutant/warnings.rb +0 -106
@@ -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,31 @@ module Mutant
27
28
  emit_type(s(:str, NULL_REGEXP_SOURCE), options)
28
29
  end
29
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
+ def mutate_body
35
+ return unless body.all?(&method(:n_str?))
36
+
37
+ Mutator.mutate(body_ast).each do |mutation|
38
+ source = AST::Regexp.to_expression(mutation).to_s
39
+ emit_type(s(:str, source), options)
40
+ end
41
+ end
42
+
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
+
30
56
  end # Regex
31
57
  end # Literal
32
58
  end # Node
@@ -11,8 +11,6 @@ module Mutant
11
11
 
12
12
  children :value
13
13
 
14
- PREFIX = '__mutant__'
15
-
16
14
  private
17
15
 
18
16
  def dispatch
@@ -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
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ module Regexp
7
+ # Mutator for regexp capture groups, such as `/(foo)/`
8
+ class CaptureGroup < Node
9
+ handle(:regexp_capture_group)
10
+
11
+ children :group
12
+
13
+ private
14
+
15
+ def dispatch
16
+ return unless group
17
+
18
+ emit(s(:regexp_passive_group, group))
19
+ emit_group_mutations
20
+ end
21
+ end # EndOfLineAnchor
22
+ end # Regexp
23
+ end # Node
24
+ end # Mutator
25
+ end # Mutant
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ module Regexp
7
+ # Character type mutator
8
+ class CharacterType < Node
9
+ map = {
10
+ regexp_digit_type: :regexp_nondigit_type,
11
+ regexp_hex_type: :regexp_nonhex_type,
12
+ regexp_space_type: :regexp_nonspace_type,
13
+ regexp_word_boundary_anchor: :regexp_nonword_boundary_anchor,
14
+ regexp_word_type: :regexp_nonword_type,
15
+ regexp_xgrapheme_type: :regexp_linebreak_type
16
+ }
17
+
18
+ MAP = IceNine.deep_freeze(map.merge(map.invert))
19
+
20
+ handle(*MAP.keys)
21
+
22
+ private
23
+
24
+ def dispatch
25
+ emit(s(MAP.fetch(node.type)))
26
+ end
27
+ end # CharacterType
28
+ end # Regexp
29
+ end # Node
30
+ end # Mutator
31
+ 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 end of line anchor `$`
8
+ class EndOfLineAnchor < Node
9
+ handle(:regexp_eol_anchor)
10
+
11
+ private
12
+
13
+ def dispatch
14
+ emit(s(:regexp_eos_anchor))
15
+ end
16
+ end # EndOfLineAnchor
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 end of line or before end of string anchor `\Z`
8
+ class EndOfStringOrBeforeEndOfLineAnchor < Node
9
+ handle(:regexp_eos_ob_eol_anchor)
10
+
11
+ private
12
+
13
+ def dispatch
14
+ emit(s(:regexp_eos_anchor))
15
+ end
16
+ end # EndOfStringOrBeforeEndOfLineAnchor
17
+ end # Regexp
18
+ end # Node
19
+ end # Mutator
20
+ end # Mutant
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ module Regexp
7
+ # Mutator for zero-or-more quantifier, `*`
8
+ class ZeroOrMore < Node
9
+ MAP = IceNine.deep_freeze(
10
+ regexp_greedy_zero_or_more: :regexp_greedy_one_or_more,
11
+ regexp_reluctant_zero_or_more: :regexp_reluctant_one_or_more,
12
+ regexp_possessive_zero_or_more: :regexp_possessive_one_or_more
13
+ )
14
+
15
+ handle(*MAP.keys)
16
+
17
+ children :min, :max, :subject
18
+
19
+ private
20
+
21
+ # Replace:
22
+ # * `/a*/` with `/a+/`
23
+ # * `/a*?/` with `/a+?/`
24
+ # * `/a*+/` with `/a++/`
25
+ def dispatch
26
+ emit(s(MAP.fetch(node.type), *children))
27
+ emit_subject_mutations
28
+ emit(subject)
29
+ end
30
+ end # ZeroOrMore
31
+ end # Regexp
32
+ end # Node
33
+ end # Mutator
34
+ end # Mutant
@@ -14,7 +14,7 @@ module Mutant
14
14
  def dispatch
15
15
  emit_body_mutations if body
16
16
  end
17
- end # Class
17
+ end # Sclass
18
18
  end # Node
19
19
  end # Mutator
20
20
  end # Mutant
@@ -14,30 +14,35 @@ module Mutant
14
14
  children :receiver, :selector
15
15
 
16
16
  SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
17
- reverse_map: %i[map each],
18
- kind_of?: %i[instance_of?],
17
+ :< => %i[== eql? equal?],
18
+ :<= => %i[< == eql? equal?],
19
+ :== => %i[eql? equal?],
20
+ :=== => %i[is_a?],
21
+ :=~ => %i[match?],
22
+ :> => %i[== eql? equal?],
23
+ :>= => %i[> == eql? equal?],
24
+ __send__: %i[public_send],
25
+ all?: %i[any?],
26
+ any?: %i[all?],
27
+ at: %i[fetch key?],
28
+ eql?: %i[equal?],
29
+ fetch: %i[key?],
30
+ flat_map: %i[map],
31
+ gsub: %i[sub],
19
32
  is_a?: %i[instance_of?],
33
+ kind_of?: %i[instance_of?],
34
+ map: %i[each],
35
+ method: %i[public_method],
36
+ match: %i[match?],
20
37
  reverse_each: %i[each],
38
+ reverse_map: %i[map each],
21
39
  reverse_merge: %i[merge],
22
- map: %i[each],
23
- flat_map: %i[map],
24
40
  send: %i[public_send __send__],
25
- __send__: %i[public_send],
26
- method: %i[public_method],
27
- gsub: %i[sub],
28
- eql?: %i[equal?],
29
- to_s: %i[to_str],
30
- to_i: %i[to_int],
31
41
  to_a: %i[to_ary],
32
42
  to_h: %i[to_hash],
33
- at: %i[fetch key?],
34
- fetch: %i[key?],
35
- values_at: %i[fetch_values],
36
- :== => %i[eql? equal?],
37
- :>= => %i[> == eql? equal?],
38
- :<= => %i[< == eql? equal?],
39
- :> => %i[== eql? equal?],
40
- :< => %i[== eql? equal?]
43
+ to_i: %i[to_int],
44
+ to_s: %i[to_str],
45
+ values_at: %i[fetch_values]
41
46
  )
42
47
 
43
48
  RECEIVER_SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
@@ -79,6 +84,7 @@ module Mutant
79
84
  end
80
85
 
81
86
  def emit_selector_specific_mutations
87
+ emit_predicate_mutations
82
88
  emit_array_mutation
83
89
  emit_static_send
84
90
  emit_const_get_mutation
@@ -88,6 +94,13 @@ module Mutant
88
94
  emit_lambda_mutation
89
95
  end
90
96
 
97
+ def emit_predicate_mutations
98
+ return unless selector.match?(/\?\z/) && !selector.equal?(:defined?)
99
+
100
+ emit(s(:true))
101
+ emit(s(:false))
102
+ end
103
+
91
104
  def emit_array_mutation
92
105
  return unless selector.equal?(:Array) && possible_kernel_method?
93
106
 
@@ -175,7 +188,11 @@ module Mutant
175
188
  end
176
189
 
177
190
  def emit_argument_propagation
178
- emit_propagation(Mutant::Util.one(arguments)) if arguments.one?
191
+ return unless arguments.one?
192
+
193
+ argument = Mutant::Util.one(arguments)
194
+
195
+ emit_propagation(argument) unless n_kwargs?(argument)
179
196
  end
180
197
 
181
198
  def mutate_receiver
@@ -6,45 +6,61 @@ module Mutant
6
6
 
7
7
  # Run async computation returning driver
8
8
  #
9
+ # @param [World] world
9
10
  # @param [Config] config
10
11
  #
11
12
  # @return [Driver]
12
- def self.async(config)
13
- shared = {
14
- var_active_jobs: shared(Variable::IVar, config, value: Set.new),
15
- var_final: shared(Variable::IVar, config),
16
- var_sink: shared(Variable::IVar, config, value: config.sink)
17
- }
13
+ def self.async(world, config)
14
+ shared = shared_state(world, config)
15
+ workers = workers(world, config, shared)
18
16
 
19
17
  Driver.new(
20
- threads: threads(config, worker(config, **shared)),
18
+ workers: workers,
19
+ threads: threads(world, config, workers),
21
20
  **shared
22
21
  )
23
22
  end
24
23
 
25
- # The worker
26
- #
27
- # @param [Config] config
28
- #
29
- # @return [Worker]
30
- def self.worker(config, **shared)
31
- Worker.new(
32
- processor: config.processor,
33
- var_running: shared(Variable::MVar, config, value: config.jobs),
34
- var_source: shared(Variable::IVar, config, value: config.source),
35
- **shared
36
- )
24
+ def self.workers(world, config, shared)
25
+ Array.new(config.jobs) do |index|
26
+ Worker.start(
27
+ block: config.block,
28
+ index: index,
29
+ process_name: "#{config.process_name}-#{index}",
30
+ world: world,
31
+ **shared
32
+ )
33
+ end
37
34
  end
35
+ private_class_method :workers
36
+
37
+ def self.shared_state(world, config)
38
+ {
39
+ var_active_jobs: shared(Variable::IVar, world, value: Set.new),
40
+ var_final: shared(Variable::IVar, world),
41
+ var_running: shared(Variable::MVar, world, value: config.jobs),
42
+ var_sink: shared(Variable::IVar, world, value: config.sink),
43
+ var_source: shared(Variable::IVar, world, value: config.source)
44
+ }
45
+ end
46
+ private_class_method :shared_state
47
+
48
+ def self.threads(world, config, workers)
49
+ thread = world.thread
38
50
 
39
- def self.threads(config, worker)
40
- Array.new(config.jobs) { config.thread.new(&worker.method(:call)) }
51
+ workers.map do |worker|
52
+ thread.new do
53
+ thread.current.name = "#{config.thread_name}-#{worker.index}"
54
+ worker.call
55
+ end
56
+ end
41
57
  end
42
58
  private_class_method :threads
43
59
 
44
- def self.shared(klass, config, **attributes)
60
+ def self.shared(klass, world, **attributes)
45
61
  klass.new(
46
- condition_variable: config.condition_variable,
47
- mutex: config.mutex,
62
+ condition_variable: world.condition_variable,
63
+ mutex: world.mutex,
48
64
  **attributes
49
65
  )
50
66
  end
@@ -75,13 +91,12 @@ module Mutant
75
91
  # Parallel run configuration
76
92
  class Config
77
93
  include Adamantium::Flat, Anima.new(
78
- :condition_variable,
94
+ :block,
79
95
  :jobs,
80
- :mutex,
81
- :processor,
96
+ :process_name,
82
97
  :sink,
83
98
  :source,
84
- :thread
99
+ :thread_name
85
100
  )
86
101
  end # Config
87
102