mutant 0.5.26 → 0.6.0

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +1 -0
  4. data/Changelog.md +16 -3
  5. data/Gemfile +0 -2
  6. data/Gemfile.devtools +2 -2
  7. data/README.md +9 -15
  8. data/bin/mutant +0 -1
  9. data/config/flay.yml +1 -1
  10. data/config/flog.yml +1 -1
  11. data/config/mutant.yml +1 -1
  12. data/config/reek.yml +14 -11
  13. data/config/rubocop.yml +1 -1
  14. data/lib/mutant.rb +22 -21
  15. data/lib/mutant/ast.rb +47 -0
  16. data/lib/mutant/cli.rb +7 -4
  17. data/lib/mutant/config.rb +1 -0
  18. data/lib/mutant/context.rb +1 -1
  19. data/lib/mutant/diff.rb +38 -7
  20. data/lib/mutant/env.rb +22 -3
  21. data/lib/mutant/expression.rb +15 -4
  22. data/lib/mutant/integration.rb +1 -1
  23. data/lib/mutant/isolation.rb +2 -4
  24. data/lib/mutant/matcher.rb +1 -1
  25. data/lib/mutant/matcher/method.rb +1 -1
  26. data/lib/mutant/matcher/method/singleton.rb +1 -1
  27. data/lib/mutant/matcher/methods.rb +0 -2
  28. data/lib/mutant/meta/example.rb +0 -2
  29. data/lib/mutant/meta/example/dsl.rb +1 -1
  30. data/lib/mutant/mutator.rb +1 -1
  31. data/lib/mutant/mutator/node.rb +3 -3
  32. data/lib/mutant/mutator/node/begin.rb +1 -1
  33. data/lib/mutant/mutator/node/block.rb +16 -3
  34. data/lib/mutant/mutator/node/if.rb +1 -1
  35. data/lib/mutant/mutator/node/literal/fixnum.rb +1 -1
  36. data/lib/mutant/mutator/node/resbody.rb +0 -2
  37. data/lib/mutant/mutator/node/send.rb +17 -7
  38. data/lib/mutant/mutator/node/send/index.rb +0 -2
  39. data/lib/mutant/mutator/registry.rb +1 -1
  40. data/lib/mutant/mutator/util.rb +1 -1
  41. data/lib/mutant/mutator/util/array.rb +1 -1
  42. data/lib/mutant/reporter.rb +13 -3
  43. data/lib/mutant/reporter/cli.rb +54 -8
  44. data/lib/mutant/reporter/cli/format.rb +197 -0
  45. data/lib/mutant/reporter/cli/printer.rb +402 -22
  46. data/lib/mutant/reporter/cli/tput.rb +27 -0
  47. data/lib/mutant/reporter/null.rb +4 -34
  48. data/lib/mutant/reporter/trace.rb +6 -38
  49. data/lib/mutant/result.rb +44 -56
  50. data/lib/mutant/runner.rb +99 -52
  51. data/lib/mutant/runner/collector.rb +134 -0
  52. data/lib/mutant/subject/method/instance.rb +12 -4
  53. data/lib/mutant/version.rb +1 -1
  54. data/lib/mutant/warning_filter.rb +0 -2
  55. data/lib/mutant/zombifier/file.rb +1 -1
  56. data/meta/block.rb +17 -1
  57. data/meta/send.rb +123 -1
  58. data/mutant-rspec.gemspec +3 -3
  59. data/mutant.gemspec +1 -1
  60. data/spec/integration/mutant/corpus_spec.rb +4 -195
  61. data/spec/integration/mutant/null_spec.rb +1 -3
  62. data/spec/integration/mutant/rspec_spec.rb +1 -3
  63. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -3
  64. data/spec/integration/mutant/zombie_spec.rb +1 -3
  65. data/spec/integrations.yml +7 -0
  66. data/spec/shared/method_matcher_behavior.rb +1 -1
  67. data/spec/spec_helper.rb +1 -0
  68. data/spec/support/compress_helper.rb +1 -0
  69. data/spec/support/corpus.rb +239 -0
  70. data/spec/support/mutation_verifier.rb +2 -4
  71. data/spec/unit/mutant/cli_spec.rb +20 -13
  72. data/spec/unit/mutant/context/root_spec.rb +1 -3
  73. data/spec/unit/mutant/context/scope/root_spec.rb +1 -3
  74. data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +1 -3
  75. data/spec/unit/mutant/diff_spec.rb +37 -19
  76. data/spec/unit/mutant/expression/method_spec.rb +5 -7
  77. data/spec/unit/mutant/expression/methods_spec.rb +5 -7
  78. data/spec/unit/mutant/expression/namespace/flat_spec.rb +6 -8
  79. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +6 -7
  80. data/spec/unit/mutant/expression_spec.rb +14 -5
  81. data/spec/unit/mutant/integration_spec.rb +14 -3
  82. data/spec/unit/mutant/isolation_spec.rb +2 -4
  83. data/spec/unit/mutant/loader/eval_spec.rb +1 -3
  84. data/spec/unit/mutant/matcher/chain_spec.rb +1 -3
  85. data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +21 -0
  86. data/spec/unit/mutant/matcher/compiler_spec.rb +28 -3
  87. data/spec/unit/mutant/matcher/filter_spec.rb +1 -3
  88. data/spec/unit/mutant/matcher/method/instance_spec.rb +3 -5
  89. data/spec/unit/mutant/matcher/method/singleton_spec.rb +22 -4
  90. data/spec/unit/mutant/matcher/methods/instance_spec.rb +7 -6
  91. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +4 -6
  92. data/spec/unit/mutant/matcher/namespace_spec.rb +1 -3
  93. data/spec/unit/mutant/matcher/null_spec.rb +1 -3
  94. data/spec/unit/mutant/mutation_spec.rb +1 -3
  95. data/spec/unit/mutant/mutator/node_spec.rb +1 -3
  96. data/spec/unit/mutant/reporter/cli_spec.rb +444 -206
  97. data/spec/unit/mutant/reporter/null_spec.rb +1 -3
  98. data/spec/unit/mutant/require_highjack_spec.rb +1 -3
  99. data/spec/unit/mutant/runner_spec.rb +42 -28
  100. data/spec/unit/mutant/subject/context_spec.rb +1 -3
  101. data/spec/unit/mutant/subject/method/instance_spec.rb +27 -19
  102. data/spec/unit/mutant/subject/method/singleton_spec.rb +49 -17
  103. data/spec/unit/mutant/subject_spec.rb +1 -3
  104. data/spec/unit/mutant/test_spec.rb +1 -3
  105. data/spec/unit/mutant/warning_expectation.rb +1 -3
  106. data/spec/unit/mutant/warning_filter_spec.rb +1 -3
  107. data/spec/unit/mutant_spec.rb +13 -3
  108. data/test_app/Gemfile.devtools +2 -2
  109. data/test_app/spec/unit/test_app/literal/string_spec.rb +1 -1
  110. metadata +10 -21
  111. data/lib/mutant/matcher/method/finder.rb +0 -72
  112. data/lib/mutant/reporter/cli/progress.rb +0 -10
  113. data/lib/mutant/reporter/cli/progress/config.rb +0 -30
  114. data/lib/mutant/reporter/cli/progress/env.rb +0 -30
  115. data/lib/mutant/reporter/cli/progress/noop.rb +0 -27
  116. data/lib/mutant/reporter/cli/progress/result.rb +0 -12
  117. data/lib/mutant/reporter/cli/progress/result/mutation.rb +0 -45
  118. data/lib/mutant/reporter/cli/progress/result/subject.rb +0 -54
  119. data/lib/mutant/reporter/cli/progress/subject.rb +0 -27
  120. data/lib/mutant/reporter/cli/registry.rb +0 -81
  121. data/lib/mutant/reporter/cli/report.rb +0 -10
  122. data/lib/mutant/reporter/cli/report/env.rb +0 -92
  123. data/lib/mutant/reporter/cli/report/mutation.rb +0 -103
  124. data/lib/mutant/reporter/cli/report/subject.rb +0 -32
  125. data/lib/mutant/reporter/cli/report/test.rb +0 -28
  126. data/lib/mutant/walker.rb +0 -53
  127. data/spec/shared/mutator_behavior.rb +0 -55
@@ -0,0 +1,27 @@
1
+ module Mutant
2
+ class Reporter
3
+ class CLI
4
+ # Interface to the optionally present tput binary
5
+ class Tput
6
+ include Adamantium, Concord::Public.new(:available, :prepare, :restore)
7
+
8
+ private_class_method :new
9
+
10
+ capture = lambda do |command|
11
+ stdout, _stderr, exitstatus = Open3.capture3(command)
12
+ stdout if exitstatus.success?
13
+ end
14
+
15
+ reset = capture.('tput reset')
16
+ save = capture.('tput sc') if reset
17
+ restore = capture.('tput rc') if save
18
+ clean = capture.('tput ed') if restore
19
+
20
+ UNAVAILABLE = new(false, nil, nil)
21
+
22
+ INSTANCE = clean ? new(true, reset + save, restore + clean) : UNAVAILABLE
23
+
24
+ end # TPUT
25
+ end # CLI
26
+ end # Reporter
27
+ end # Mutant
@@ -5,40 +5,10 @@ module Mutant
5
5
  class Null < self
6
6
  include Equalizer.new
7
7
 
8
- # Write warning message
9
- #
10
- # @param [String] _message
11
- #
12
- # @return [self]
13
- #
14
- # @api private
15
- #
16
- def warn(_message)
17
- self
18
- end
19
-
20
- # Report object
21
- #
22
- # @param [Object] _object
23
- #
24
- # @return [self]
25
- #
26
- # @api private
27
- #
28
- def report(_object)
29
- self
30
- end
31
-
32
- # Report progress on object
33
- #
34
- # @param [Object] _object
35
- #
36
- # @return [self]
37
- #
38
- # @api private
39
- #
40
- def progress(_object)
41
- self
8
+ %w[warn report start progress].each do |name|
9
+ define_method name do |_object|
10
+ self
11
+ end
42
12
  end
43
13
 
44
14
  end # Null
@@ -2,7 +2,7 @@ module Mutant
2
2
  class Reporter
3
3
  # Reporter to trace report calls, used as a spec adapter
4
4
  class Trace
5
- include Adamantium::Mutable, Anima.new(:progress_calls, :report_calls, :warn_calls)
5
+ include Adamantium::Mutable, Anima.new(:start_calls, :progress_calls, :report_calls, :warn_calls)
6
6
 
7
7
  # Return new trace reporter
8
8
  #
@@ -14,43 +14,11 @@ module Mutant
14
14
  super(Hash[anima.attribute_names.map { |name| [name, []] }])
15
15
  end
16
16
 
17
- # Warn with message
18
- #
19
- # @param [String] message
20
- #
21
- # @return [self]
22
- #
23
- # @api private
24
- #
25
- def warn(message)
26
- warn_calls << message
27
- self
28
- end
29
-
30
- # Report object
31
- #
32
- # @param [Object] object
33
- #
34
- # @return [self]
35
- #
36
- # @api private
37
- #
38
- def report(object)
39
- report_calls << object
40
- self
41
- end
42
-
43
- # Report new progress on object
44
- #
45
- # @param [Object] object
46
- #
47
- # @return [self]
48
- #
49
- # @api private
50
- #
51
- def progress(object)
52
- progress_calls << object
53
- self
17
+ %w[start progress report warn].each do |name|
18
+ define_method(name) do |object|
19
+ public_send(:"#{name}_calls") << object
20
+ self
21
+ end
54
22
  end
55
23
 
56
24
  end # Tracker
@@ -12,9 +12,9 @@ module Mutant
12
12
  # @api private
13
13
  #
14
14
  def coverage
15
- return Rational(0) if amount_mutations.zero?
15
+ return Rational(0) if amount_mutation_results.zero?
16
16
 
17
- Rational(amount_mutations_killed, amount_mutations)
17
+ Rational(amount_mutations_killed, amount_mutation_results)
18
18
  end
19
19
 
20
20
  # Hook called when module gets included
@@ -31,22 +31,12 @@ module Mutant
31
31
  host.memoize :coverage
32
32
  end
33
33
 
34
- end
35
-
36
- # Test if operation is failing
37
- #
38
- # @return [Boolean]
39
- #
40
- # @api private
41
- #
42
- def fail?
43
- !success?
44
- end
34
+ end # Coverage
45
35
 
46
36
  # Class level mixin
47
37
  module ClassMethods
48
38
 
49
- # Generate a sum messot from name and collection
39
+ # Generate a sum method from name and collection
50
40
  #
51
41
  # @param [Symbol] name
52
42
  # the attribute name on collection item and method name to use
@@ -64,6 +54,28 @@ module Mutant
64
54
  end
65
55
  memoize name
66
56
  end
57
+
58
+ # Compute result tracking runtime
59
+ #
60
+ # @return [Result]
61
+ #
62
+ # @api private
63
+ #
64
+ def compute
65
+ start = Time.now
66
+ new(yield.merge(runtime: Time.now - start))
67
+ end
68
+
69
+ end # ClassMethods
70
+
71
+ # Test if operation is failing
72
+ #
73
+ # @return [Boolean]
74
+ #
75
+ # @api private
76
+ #
77
+ def fail?
78
+ !success?
67
79
  end
68
80
 
69
81
  # Return overhead
@@ -78,7 +90,7 @@ module Mutant
78
90
 
79
91
  # Hook called when module gets included
80
92
  #
81
- # @param [Class, Module] hosto
93
+ # @param [Class, Module] host
82
94
  #
83
95
  # @return [undefined]
84
96
  #
@@ -93,11 +105,11 @@ module Mutant
93
105
 
94
106
  # Env result object
95
107
  class Env
96
- include Coverage, Result, Anima.new(:runtime, :env, :subject_results)
108
+ include Coverage, Result, Anima.new(:runtime, :env, :subject_results, :done)
97
109
 
98
110
  COVERAGE_PRECISION = 1
99
111
 
100
- # Test if run was successful
112
+ # Test if run is successful
101
113
  #
102
114
  # @return [Boolean]
103
115
  #
@@ -119,6 +131,7 @@ module Mutant
119
131
  end
120
132
 
121
133
  sum :amount_mutations, :subject_results
134
+ sum :amount_mutation_results, :subject_results
122
135
  sum :amount_mutations_alive, :subject_results
123
136
  sum :amount_mutations_killed, :subject_results
124
137
  sum :killtime, :subject_results
@@ -137,7 +150,7 @@ module Mutant
137
150
 
138
151
  # Test result
139
152
  class Test
140
- include Result, Adamantium::Flat, Anima::Update, Anima.new(
153
+ include Result, Anima.new(
141
154
  :test,
142
155
  :output,
143
156
  :mutation,
@@ -145,16 +158,6 @@ module Mutant
145
158
  :runtime
146
159
  )
147
160
 
148
- # NOTE:
149
- #
150
- # The test is intentionally NOT part of the mashalled data.
151
- # In rspec the example group cannot deterministically being marshalled, because
152
- # they reference a crazy mix of IO objects, global objects etc.
153
- #
154
- MARSHALLED_IVARS = (anima.attribute_names - [:test]).map do |name|
155
- :"@#{name}"
156
- end
157
-
158
161
  # Return killtime
159
162
  #
160
163
  # @return [Float]
@@ -173,31 +176,6 @@ module Mutant
173
176
  mutation.killed_by?(self)
174
177
  end
175
178
 
176
- # Return marshallable data
177
- #
178
- #
179
- # @return [Array]
180
- #
181
- # @api private
182
- #
183
- def marshal_dump
184
- MARSHALLED_IVARS.map(&method(:instance_variable_get))
185
- end
186
-
187
- # Load marshalled data
188
- #
189
- # @param [Array] array
190
- #
191
- # @return [undefined]
192
- #
193
- # @api private
194
- #
195
- def marshal_load(array)
196
- MARSHALLED_IVARS.zip(array) do |instance_variable_name, value|
197
- instance_variable_set(instance_variable_name, value)
198
- end
199
- end
200
-
201
179
  end # Test
202
180
 
203
181
  # Subject result
@@ -227,6 +205,16 @@ module Mutant
227
205
  end
228
206
  memoize :alive_mutation_results
229
207
 
208
+ # Return amount of mutations
209
+ #
210
+ # @return [Fixnum]
211
+ #
212
+ # @api private
213
+ #
214
+ def amount_mutation_results
215
+ mutation_results.length
216
+ end
217
+
230
218
  # Return amount of mutations
231
219
  #
232
220
  # @return [Fixnum]
@@ -254,7 +242,7 @@ module Mutant
254
242
  # @api private
255
243
  #
256
244
  def amount_mutations_alive
257
- amount_mutations - amount_mutations_killed
245
+ alive_mutation_results.length
258
246
  end
259
247
 
260
248
  # Return alive mutations
@@ -272,9 +260,9 @@ module Mutant
272
260
 
273
261
  # Mutation result
274
262
  class Mutation
275
- include Result, Anima.new(:runtime, :mutation, :test_results)
263
+ include Result, Anima.new(:runtime, :mutation, :test_results, :index)
276
264
 
277
- # Test if mutation was handeled successfully
265
+ # Test if mutation was handled successfully
278
266
  #
279
267
  # @return [Boolean]
280
268
  #
@@ -1,7 +1,7 @@
1
1
  module Mutant
2
2
  # Runner baseclass
3
3
  class Runner
4
- include Adamantium, Concord.new(:env), Procto.call(:result)
4
+ include Adamantium::Flat, Concord.new(:env), Procto.call(:result)
5
5
 
6
6
  # Initialize object
7
7
  #
@@ -12,21 +12,19 @@ module Mutant
12
12
  def initialize(env)
13
13
  super
14
14
 
15
- @stop = false
15
+ @collector = Collector.new(env)
16
+ @mutex = Mutex.new
17
+ @mutations = env.mutations.dup
16
18
 
17
19
  config.integration.setup
18
20
 
19
- progress(env)
21
+ config.reporter.start(env)
20
22
 
21
- start = Time.now
23
+ run
22
24
 
23
- @result = Result::Env.new(
24
- env: env,
25
- subject_results: visit_collection(env.subjects, &method(:run_subject)),
26
- runtime: Time.now - start
27
- ).tap do |report|
28
- config.reporter.report(report)
29
- end
25
+ @result = @collector.result.update(done: true)
26
+
27
+ config.reporter.report(result)
30
28
  end
31
29
 
32
30
  # Return result
@@ -39,79 +37,128 @@ module Mutant
39
37
 
40
38
  private
41
39
 
42
- # Run subject
40
+ # Run mutation analysis
43
41
  #
44
42
  # @return [Report::Subject]
45
43
  #
46
44
  # @api private
47
45
  #
48
- def run_subject(subject)
49
- Result::Subject.new(
50
- subject: subject,
51
- mutation_results: visit_collection(subject.mutations, &method(:run_mutation)),
52
- runtime: nil
46
+ def run
47
+ Parallel.map(
48
+ @mutations,
49
+ in_processes: config.processes,
50
+ finish: method(:finish),
51
+ start: method(:start),
52
+ &method(:run_mutation)
53
53
  )
54
54
  end
55
55
 
56
- # Run mutation
56
+ # Handle started mutation
57
57
  #
58
- # @return [Report::Mutation]
58
+ # @param [Mutation] mutation
59
+ # @param [Fixnum] _index
60
+ #
61
+ # @return [undefined]
59
62
  #
60
63
  # @api private
61
64
  #
62
- def run_mutation(mutation)
63
- start = Time.now
64
- test_results = mutation.subject.tests.each_with_object([]) do |test, results|
65
- results << result = run_mutation_test(mutation, test).tap(&method(:progress))
66
- break results if mutation.killed_by?(result)
65
+ def start(mutation, _index)
66
+ @mutex.synchronize do
67
+ @collector.start(mutation)
67
68
  end
69
+ end
68
70
 
69
- Result::Mutation.new(
70
- mutation: mutation,
71
- runtime: Time.now - start,
72
- test_results: test_results
73
- )
71
+ # Handle finished mutation
72
+ #
73
+ # @param [Mutation] mutation
74
+ # @param [Fixnum] index
75
+ # @param [Object] result
76
+ #
77
+ # @return [undefined]
78
+ #
79
+ # @api private
80
+ #
81
+ def finish(mutation, index, result)
82
+ return unless result.kind_of?(Mutant::Result::Mutation)
83
+
84
+ test_results = result.test_results.zip(mutation.subject.tests).map do |test_result, test|
85
+ test_result.update(test: test, mutation: mutation) if test_result
86
+ end.compact
87
+
88
+ @mutex.synchronize do
89
+ process_result(result.update(index: index, mutation: mutation, test_results: test_results))
90
+ end
74
91
  end
75
92
 
76
- # Return config
93
+ # Process result
77
94
  #
78
- # @return [Config]
95
+ # @param [Result::Mutation] result
96
+ #
97
+ # @return [undefined]
79
98
  #
80
99
  # @api private
81
100
  #
82
- def config
83
- env.config
101
+ def process_result(result)
102
+ @collector.finish(result)
103
+ config.reporter.progress(@collector)
104
+ handle_exit(result)
84
105
  end
85
106
 
86
- # Visit collection
107
+ # Handle exit if needed
108
+ #
109
+ # @param [Result::Mutation] mutation
87
110
  #
88
- # @return [Array<Result>]
111
+ # @return [undefined]
89
112
  #
90
113
  # @api private
91
114
  #
92
- def visit_collection(collection)
93
- results = []
115
+ def handle_exit(result)
116
+ return if !config.fail_fast || result.success?
94
117
 
95
- collection.each do |item|
96
- progress(item)
97
- start = Time.now
98
- results << result = yield(item).update(runtime: Time.now - start).tap(&method(:progress))
99
- break if @stop ||= config.fail_fast? && result.fail?
100
- end
118
+ @mutations.clear
119
+ end
101
120
 
102
- results
121
+ # Run mutation
122
+ #
123
+ # @param [Mutation] mutation
124
+ #
125
+ # @return [Report::Mutation]
126
+ #
127
+ # @api private
128
+ #
129
+ def run_mutation(mutation)
130
+ Result::Mutation.compute do
131
+ {
132
+ index: nil,
133
+ mutation: nil,
134
+ test_results: kill_mutation(mutation)
135
+ }
136
+ end
103
137
  end
104
138
 
105
- # Report progress
139
+ # Kill mutation
106
140
  #
107
- # @param [Object] object
141
+ # @param [Mutation] mutation
108
142
  #
109
- # @return [undefined]
143
+ # @return [Array<Result::Test>]
110
144
  #
111
145
  # @api private
112
146
  #
113
- def progress(object)
114
- config.reporter.progress(object)
147
+ def kill_mutation(mutation)
148
+ mutation.subject.tests.each_with_object([]) do |test, results|
149
+ results << result = run_mutation_test(mutation, test)
150
+ return results if mutation.killed_by?(result)
151
+ end
152
+ end
153
+
154
+ # Return config
155
+ #
156
+ # @return [Config]
157
+ #
158
+ # @api private
159
+ #
160
+ def config
161
+ env.config
115
162
  end
116
163
 
117
164
  # Return test result
@@ -125,11 +172,11 @@ module Mutant
125
172
  config.isolation.call do
126
173
  mutation.insert
127
174
  test.run
128
- end.update(test: test, mutation: mutation)
175
+ end
129
176
  rescue Isolation::Error => exception
130
177
  Result::Test.new(
131
- test: test,
132
- mutation: mutation,
178
+ test: nil,
179
+ mutation: nil,
133
180
  runtime: Time.now - time,
134
181
  output: exception.message,
135
182
  passed: false