mutant 0.5.26 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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