mutant 0.11.16 → 0.11.18

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mutant +55 -46
  3. data/lib/mutant/bootstrap.rb +66 -35
  4. data/lib/mutant/cli/command/environment/run.rb +1 -1
  5. data/lib/mutant/cli/command/environment.rb +2 -2
  6. data/lib/mutant/cli/command.rb +24 -11
  7. data/lib/mutant/env.rb +9 -0
  8. data/lib/mutant/mutator/node/literal/regex.rb +8 -8
  9. data/lib/mutant/mutator/node/send.rb +27 -17
  10. data/lib/mutant/mutator/regexp.rb +211 -0
  11. data/lib/mutant/parallel/driver.rb +1 -0
  12. data/lib/mutant/parallel/worker.rb +5 -1
  13. data/lib/mutant/runner.rb +8 -6
  14. data/lib/mutant/segment/recorder.rb +124 -0
  15. data/lib/mutant/segment.rb +25 -0
  16. data/lib/mutant/version.rb +1 -1
  17. data/lib/mutant/world.rb +5 -0
  18. data/lib/mutant.rb +288 -228
  19. metadata +12 -33
  20. data/lib/mutant/ast/regexp/transformer/direct.rb +0 -145
  21. data/lib/mutant/ast/regexp/transformer/named_group.rb +0 -50
  22. data/lib/mutant/ast/regexp/transformer/options_group.rb +0 -68
  23. data/lib/mutant/ast/regexp/transformer/quantifier.rb +0 -92
  24. data/lib/mutant/ast/regexp/transformer/recursive.rb +0 -56
  25. data/lib/mutant/ast/regexp/transformer/root.rb +0 -28
  26. data/lib/mutant/ast/regexp/transformer/text.rb +0 -58
  27. data/lib/mutant/ast/regexp/transformer.rb +0 -152
  28. data/lib/mutant/ast/regexp.rb +0 -54
  29. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +0 -20
  30. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +0 -20
  31. data/lib/mutant/mutator/node/regexp/capture_group.rb +0 -23
  32. data/lib/mutant/mutator/node/regexp/character_type.rb +0 -31
  33. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +0 -20
  34. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +0 -20
  35. data/lib/mutant/mutator/node/regexp/named_group.rb +0 -39
  36. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +0 -34
  37. data/lib/mutant/mutator/node/regexp.rb +0 -20
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Regexp < self
6
+ def self.regexp_body(node)
7
+ *body, _options = node.children
8
+
9
+ body.map do |child|
10
+ return unless child.type.equal?(:str)
11
+ child.children
12
+ end.join
13
+ end
14
+
15
+ class Registry
16
+ include Concord.new(:contents)
17
+
18
+ def initialize
19
+ super({})
20
+ end
21
+
22
+ def register(expression_class, mutator_class)
23
+ (contents[expression_class] ||= []) << mutator_class
24
+ self
25
+ end
26
+
27
+ def lookup(expression_class)
28
+ contents.fetch(expression_class, []).push(Quantifier)
29
+ end
30
+ end # Registry
31
+
32
+ REGISTRY = Registry.new
33
+
34
+ # mutant:disable - boot time code
35
+ def self.handle(*types)
36
+ types.each do |type|
37
+ self::REGISTRY.register(type, self)
38
+ end
39
+ end
40
+
41
+ private_class_method :handle
42
+
43
+ def self.mutate(expression)
44
+ REGISTRY
45
+ .lookup(expression.class)
46
+ .map { |mutator| mutator.call(input: expression, parent: nil) }
47
+ .reduce(&:merge)
48
+ end
49
+
50
+ private
51
+
52
+ def subexpressions
53
+ input.expressions
54
+ end
55
+
56
+ def mk_dup
57
+ Marshal.load(Marshal.dump(input))
58
+ end
59
+
60
+ def emit_expression(klass:, text:)
61
+ emit(
62
+ klass.construct(text: text).tap do |new|
63
+ subexpressions.each do |expression|
64
+ new << Marshal.load(Marshal.dump(expression))
65
+ end
66
+ end
67
+ )
68
+ end
69
+
70
+ def emit_passive_group
71
+ emit_expression(
72
+ klass: ::Regexp::Expression::Group::Passive,
73
+ text: '(?:'
74
+ )
75
+ end
76
+
77
+ class Alternation < self
78
+ handle(::Regexp::Expression::Alternation)
79
+
80
+ def dispatch
81
+ subexpressions.each_index do |index|
82
+ emit(mk_dup.tap { |new| new.expressions.delete_at(index) })
83
+ end
84
+ end
85
+ end
86
+
87
+ class Quantifier < self
88
+ MAP = {
89
+ '*' => '+',
90
+ '*+' => '++',
91
+ '*?' => '+?'
92
+ }.freeze
93
+
94
+ def dispatch
95
+ return unless input.quantifier
96
+
97
+ emit_removal
98
+ emit_replacement
99
+ end
100
+
101
+ def emit_removal
102
+ emit(mk_dup.tap { |new| new.quantifier = nil })
103
+ end
104
+
105
+ def emit_replacement
106
+ new_text = MAP[input.quantifier.text]
107
+
108
+ emit(mk_dup.tap { |new| new.quantifier.text = new_text })
109
+ end
110
+ end
111
+
112
+ class Replacement < self
113
+ ns = ::Regexp::Expression
114
+
115
+ dual_table =
116
+ [
117
+ [ns::Anchor::WordBoundary, '\\b', ns::Anchor::NonWordBoundary, '\\B'],
118
+ [ns::CharacterType::Digit, '\\d', ns::CharacterType::NonDigit, '\\D'],
119
+ [ns::CharacterType::ExtendedGrapheme, '\\X', ns::CharacterType::Linebreak, '\\R'],
120
+ [ns::CharacterType::Hex, '\\h', ns::CharacterType::NonHex, '\\H'],
121
+ [ns::CharacterType::Space, '\\s', ns::CharacterType::NonSpace, '\\S'],
122
+ [ns::CharacterType::Word, '\\w', ns::CharacterType::NonWord, '\\W']
123
+ ]
124
+
125
+ MAP = dual_table.flat_map do |(left_key, left_text, right_key, right_text)|
126
+ [
127
+ [left_key, [right_key, right_text]],
128
+ [right_key, [left_key, left_text]]
129
+ ]
130
+ end.to_h
131
+
132
+ MAP[ns::Anchor::BeginningOfLine] = [ns::Anchor::BeginningOfString, '\\A']
133
+ MAP[ns::Anchor::EndOfLine] = [ns::Anchor::EndOfString, '\\z']
134
+ MAP[ns::Anchor::EndOfStringOrBeforeEndOfLine] = [ns::Anchor::EndOfString, '\\z']
135
+
136
+ MAP.freeze
137
+
138
+ handle(*MAP.keys)
139
+
140
+ def dispatch
141
+ klass, text = MAP.fetch(input.class)
142
+
143
+ emit(klass.construct(text: text).tap { |new| new.quantifier = input.quantifier })
144
+ end
145
+ end
146
+
147
+ class GroupCapturePositional < self
148
+ handle(::Regexp::Expression::Group::Capture)
149
+
150
+ private
151
+
152
+ def dispatch
153
+ emit_passive_group unless subexpressions.empty?
154
+ end
155
+ end
156
+
157
+ class GroupCaptureNamed < self
158
+ handle(::Regexp::Expression::Group::Named)
159
+
160
+ private
161
+
162
+ def dispatch
163
+ return if input.name.start_with?('_') || subexpressions.empty?
164
+
165
+ emit_passive_group
166
+
167
+ emit_expression(
168
+ klass: ::Regexp::Expression::Group::Named,
169
+ text: "(?<_#{input.name}>"
170
+ )
171
+ end
172
+ end
173
+
174
+ class Recurse < self
175
+ ns = ::Regexp::Expression
176
+
177
+ # This list of nodes with subexessions is not yet complete.
178
+ handle(ns::Assertion::Base)
179
+ handle(ns::Assertion::Lookahead)
180
+ handle(ns::Assertion::Lookbehind)
181
+ handle(ns::Assertion::NegativeLookahead)
182
+ handle(ns::Assertion::NegativeLookbehind)
183
+ handle(ns::Alternative)
184
+ handle(ns::Alternation)
185
+ handle(ns::Sequence)
186
+ handle(ns::CharacterSet)
187
+ handle(ns::CharacterSet::Range)
188
+ handle(ns::Conditional::Branch)
189
+ handle(ns::Conditional::Expression)
190
+ handle(ns::Group::Absence)
191
+ handle(ns::Group::Atomic)
192
+ handle(ns::Group::Capture)
193
+ handle(ns::Group::Comment)
194
+ handle(ns::Group::Named)
195
+ handle(ns::Group::Options)
196
+ handle(ns::Group::Passive)
197
+ handle(ns::Root)
198
+
199
+ private
200
+
201
+ def dispatch
202
+ subexpressions.each_with_index do |expression, index|
203
+ self.class.mutate(expression).each do |new_expression|
204
+ emit(mk_dup.tap { |new| new.expressions[index] = new_expression })
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end # Regexp
210
+ end # Mutator
211
+ end # Mutant
@@ -49,6 +49,7 @@ module Mutant
49
49
  def finalize(status)
50
50
  status.tap do
51
51
  if status.done?
52
+ workers.each(&:signal)
52
53
  workers.each(&:join)
53
54
  threads.each(&:join)
54
55
  end
@@ -77,8 +77,12 @@ module Mutant
77
77
  self
78
78
  end
79
79
 
80
- def join
80
+ def signal
81
81
  process.kill('TERM', pid)
82
+ self
83
+ end
84
+
85
+ def join
82
86
  process.wait(pid)
83
87
  self
84
88
  end
data/lib/mutant/runner.rb CHANGED
@@ -15,15 +15,17 @@ module Mutant
15
15
  def self.run_mutation_analysis(env)
16
16
  reporter = reporter(env)
17
17
 
18
- run_driver(
19
- reporter,
20
- Parallel.async(env.world, mutation_test_config(env))
21
- ).tap do |result|
22
- reporter.report(result)
23
- end
18
+ env
19
+ .record(:analysis) { run_driver(reporter, async_driver(env)) }
20
+ .tap { |result| env.record(:report) { reporter.report(result) } }
24
21
  end
25
22
  private_class_method :run_mutation_analysis
26
23
 
24
+ def self.async_driver(env)
25
+ Parallel.async(env.world, mutation_test_config(env))
26
+ end
27
+ private_class_method :async_driver
28
+
27
29
  def self.run_driver(reporter, driver)
28
30
  Signal.trap('INT') do
29
31
  driver.stop
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Segment
5
+ class Recorder
6
+ include Anima.new(
7
+ :gen_id,
8
+ :parent_id,
9
+ :recording_start,
10
+ :root_id,
11
+ :segments,
12
+ :timer
13
+ )
14
+
15
+ private(*anima.attribute_names)
16
+
17
+ # rubocop:disable Metrics/MethodLength
18
+ def record(name)
19
+ start = timer.now
20
+ parent_id = parent_id()
21
+
22
+ @parent_id = id = gen_id.call
23
+
24
+ yield.tap do
25
+ segments << Segment.new(
26
+ id: id,
27
+ name: name,
28
+ parent_id: parent_id,
29
+ timestamp_end: timer.now,
30
+ timestamp_start: start
31
+ )
32
+ end
33
+ ensure
34
+ @parent_id = parent_id
35
+ end
36
+ # rubocop:enable Metrics/MethodLength
37
+
38
+ def print_profile(io)
39
+ print_node(io, tree, 0)
40
+ end
41
+
42
+ private
43
+
44
+ class Node
45
+ include Adamantium, Anima.new(:value, :children)
46
+ end
47
+ private_constant :Node
48
+
49
+ def tree
50
+ id_index = {}
51
+ parent_index = {}
52
+
53
+ final_segments.each do |segment|
54
+ id_index[segment.id] = segment
55
+
56
+ (parent_index[segment.parent_id] ||= []) << segment
57
+ end
58
+
59
+ build_node(
60
+ value: id_index.fetch(root_id),
61
+ parent_index: parent_index
62
+ )
63
+ end
64
+
65
+ def final_segments
66
+ timestamp_end = timer.now
67
+
68
+ segments.map do |segment|
69
+ if segment.timestamp_end
70
+ segment
71
+ else
72
+ segment.with(timestamp_end: timestamp_end)
73
+ end
74
+ end
75
+ end
76
+
77
+ def build_node(value:, parent_index:)
78
+ Node.new(
79
+ value: value,
80
+ children: build_children(
81
+ parent_id: value.id,
82
+ parent_index: parent_index
83
+ )
84
+ )
85
+ end
86
+
87
+ def build_children(parent_id:, parent_index:)
88
+ parent_index
89
+ .fetch(parent_id, EMPTY_ARRAY)
90
+ .map { |value| build_node(value: value, parent_index: parent_index) }
91
+ end
92
+
93
+ def print_node(io, node, indent)
94
+ segment = node.value
95
+
96
+ indent_str = ' ' * indent
97
+
98
+ print_line(io, :offset_start, segment, indent_str)
99
+
100
+ return unless node.children.any?
101
+
102
+ node.children.each do |child|
103
+ print_node(io, child, indent.succ)
104
+ end
105
+ print_line(io, :offset_end, segment, indent_str)
106
+ end
107
+
108
+ # rubocop:disable Metrics/ParameterLists
109
+ # rubocop:disable Style/FormatStringToken
110
+ def print_line(io, offset, segment, indent_str)
111
+ io.puts(
112
+ '%4.4f: (%4.4fs) %s %s' % [
113
+ segment.public_send(offset, recording_start),
114
+ segment.elapsed,
115
+ indent_str,
116
+ segment.name
117
+ ]
118
+ )
119
+ end
120
+ # rubocop:enable Metrics/ParameterLists
121
+ # rubocop:enable Style/FormatStringToken
122
+ end # Recorder
123
+ end # Segment
124
+ end # Mutant
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Segment
5
+ include Adamantium, Anima.new(
6
+ :id,
7
+ :name,
8
+ :parent_id,
9
+ :timestamp_end,
10
+ :timestamp_start
11
+ )
12
+
13
+ def elapsed
14
+ timestamp_end - timestamp_start
15
+ end
16
+
17
+ def offset_start(recording_start)
18
+ timestamp_start - recording_start
19
+ end
20
+
21
+ def offset_end(recording_start)
22
+ timestamp_end - recording_start
23
+ end
24
+ end # Segment
25
+ end # Mutant
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.11.16'
5
+ VERSION = '0.11.18'
6
6
  end # Mutant
data/lib/mutant/world.rb CHANGED
@@ -19,6 +19,7 @@ module Mutant
19
19
  :pathname,
20
20
  :process,
21
21
  :random,
22
+ :recorder,
22
23
  :stderr,
23
24
  :stdout,
24
25
  :thread,
@@ -77,5 +78,9 @@ module Mutant
77
78
  Timer::Deadline::None.new
78
79
  end
79
80
  end
81
+
82
+ def record(name, &block)
83
+ recorder.record(name, &block)
84
+ end
80
85
  end # World
81
86
  end # Mutant