mutant 0.6.7 → 0.7.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +10 -0
  3. data/README.md +1 -1
  4. data/config/flay.yml +1 -1
  5. data/config/reek.yml +11 -40
  6. data/config/rubocop.yml +1 -1
  7. data/lib/mutant.rb +10 -2
  8. data/lib/mutant/actor.rb +64 -0
  9. data/lib/mutant/actor/actor.rb +50 -0
  10. data/lib/mutant/actor/env.rb +35 -0
  11. data/lib/mutant/actor/mailbox.rb +53 -0
  12. data/lib/mutant/actor/receiver.rb +48 -0
  13. data/lib/mutant/actor/sender.rb +27 -0
  14. data/lib/mutant/ast/types.rb +1 -1
  15. data/lib/mutant/cli.rb +2 -0
  16. data/lib/mutant/config.rb +2 -1
  17. data/lib/mutant/env.rb +6 -2
  18. data/lib/mutant/expression/methods.rb +1 -1
  19. data/lib/mutant/integration.rb +11 -1
  20. data/lib/mutant/isolation.rb +1 -2
  21. data/lib/mutant/meta/example.rb +1 -1
  22. data/lib/mutant/mutation.rb +47 -21
  23. data/lib/mutant/mutator/node.rb +1 -1
  24. data/lib/mutant/mutator/node/literal/symbol.rb +1 -1
  25. data/lib/mutant/mutator/node/send.rb +4 -3
  26. data/lib/mutant/reporter/cli.rb +2 -0
  27. data/lib/mutant/reporter/cli/format.rb +23 -36
  28. data/lib/mutant/reporter/cli/printer.rb +66 -27
  29. data/lib/mutant/result.rb +45 -58
  30. data/lib/mutant/runner.rb +47 -154
  31. data/lib/mutant/runner/master.rb +174 -0
  32. data/lib/mutant/runner/scheduler.rb +141 -0
  33. data/lib/mutant/runner/worker.rb +93 -0
  34. data/lib/mutant/subject/method/instance.rb +1 -15
  35. data/lib/mutant/test.rb +2 -15
  36. data/lib/mutant/version.rb +1 -1
  37. data/meta/send.rb +16 -0
  38. data/mutant-rspec.gemspec +1 -1
  39. data/mutant.gemspec +1 -1
  40. data/spec/integration/mutant/rspec_spec.rb +0 -6
  41. data/spec/spec_helper.rb +9 -1
  42. data/spec/support/fake_actor.rb +93 -0
  43. data/spec/support/shared_context.rb +135 -0
  44. data/spec/unit/mutant/actor/actor_spec.rb +35 -0
  45. data/spec/unit/mutant/actor/binding_spec.rb +32 -0
  46. data/spec/unit/mutant/actor/env_spec.rb +49 -0
  47. data/spec/unit/mutant/actor/message_spec.rb +23 -0
  48. data/spec/unit/mutant/actor/receiver_spec.rb +60 -0
  49. data/spec/unit/mutant/actor/sender_spec.rb +22 -0
  50. data/spec/unit/mutant/cli_spec.rb +17 -4
  51. data/spec/unit/mutant/env_spec.rb +2 -2
  52. data/spec/unit/mutant/mailbox_spec.rb +33 -0
  53. data/spec/unit/mutant/mutation_spec.rb +52 -18
  54. data/spec/unit/mutant/mutator/registry_spec.rb +4 -4
  55. data/spec/unit/mutant/reporter/cli_spec.rb +131 -249
  56. data/spec/unit/mutant/result/env_spec.rb +55 -0
  57. data/spec/unit/mutant/result/subject_spec.rb +43 -0
  58. data/spec/unit/mutant/runner/master_spec.rb +199 -0
  59. data/spec/unit/mutant/runner/scheduler_spec.rb +161 -0
  60. data/spec/unit/mutant/runner/worker_spec.rb +73 -0
  61. data/spec/unit/mutant/runner_spec.rb +60 -118
  62. data/spec/unit/mutant/subject/method/instance_spec.rb +18 -31
  63. data/spec/unit/mutant/warning_filter_spec.rb +1 -1
  64. metadata +39 -14
  65. data/lib/mutant/runner/collector.rb +0 -133
  66. data/lib/mutant/warning_expectation.rb +0 -47
  67. data/spec/unit/mutant/runner/collector_spec.rb +0 -198
  68. data/spec/unit/mutant/test_spec.rb +0 -23
  69. data/spec/unit/mutant/warning_expectation_spec.rb +0 -80
  70. data/test_app/Gemfile.rspec2 +0 -6
data/lib/mutant/result.rb CHANGED
@@ -54,30 +54,8 @@ module Mutant
54
54
  end
55
55
  memoize name
56
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
57
  end # ClassMethods
70
58
 
71
- # Test if operation is failing
72
- #
73
- # @return [Boolean]
74
- #
75
- # @api private
76
- #
77
- def fail?
78
- !success?
79
- end
80
-
81
59
  # Return overhead
82
60
  #
83
61
  # @return [Float]
@@ -98,7 +76,7 @@ module Mutant
98
76
  #
99
77
  def self.included(host)
100
78
  host.class_eval do
101
- include Adamantium::Flat, Anima::Update
79
+ include Adamantium, Anima::Update
102
80
  extend ClassMethods
103
81
  end
104
82
  end
@@ -120,6 +98,16 @@ module Mutant
120
98
  end
121
99
  memoize :success?
122
100
 
101
+ # Test if runner should continue on env
102
+ #
103
+ # @return [Boolean]
104
+ #
105
+ # @api private
106
+ #
107
+ def continue?
108
+ !env.config.fail_fast || subject_results.all?(&:success?)
109
+ end
110
+
123
111
  # Return failed subject results
124
112
  #
125
113
  # @return [Array<Result::Subject>]
@@ -130,12 +118,21 @@ module Mutant
130
118
  subject_results.reject(&:success?)
131
119
  end
132
120
 
133
- sum :amount_mutations, :subject_results
134
121
  sum :amount_mutation_results, :subject_results
135
122
  sum :amount_mutations_alive, :subject_results
136
123
  sum :amount_mutations_killed, :subject_results
137
124
  sum :killtime, :subject_results
138
125
 
126
+ # Return amount of mutations
127
+ #
128
+ # @return [Fixnum]
129
+ #
130
+ # @api private
131
+ #
132
+ def amount_mutations
133
+ env.mutations.length
134
+ end
135
+
139
136
  # Return amount of subjects
140
137
  #
141
138
  # @return [Fixnum]
@@ -151,38 +148,19 @@ module Mutant
151
148
  # Test result
152
149
  class Test
153
150
  include Result, Anima.new(
154
- :test,
151
+ :tests,
155
152
  :output,
156
- :mutation,
157
153
  :passed,
158
154
  :runtime
159
155
  )
160
-
161
- # Return killtime
162
- #
163
- # @return [Float]
164
- #
165
- # @api private
166
- #
167
- alias_method :killtime, :runtime
168
-
169
- # Test if mutation test result is successful
170
- #
171
- # @return [Boolean]
172
- #
173
- # @api private
174
- #
175
- def success?
176
- mutation.killed_by?(self)
177
- end
178
-
179
156
  end # Test
180
157
 
181
158
  # Subject result
182
159
  class Subject
183
- include Coverage, Result, Anima.new(:subject, :mutation_results, :runtime)
160
+ include Coverage, Result, Anima.new(:subject, :mutation_results)
184
161
 
185
162
  sum :killtime, :mutation_results
163
+ sum :runtime, :mutation_results
186
164
 
187
165
  # Test if subject was processed successful
188
166
  #
@@ -194,6 +172,16 @@ module Mutant
194
172
  alive_mutation_results.empty?
195
173
  end
196
174
 
175
+ # Test if runner should continue on subject
176
+ #
177
+ # @return [Boolean]
178
+ #
179
+ # @api private
180
+ #
181
+ def continue?
182
+ mutation_results.all?(&:success?)
183
+ end
184
+
197
185
  # Return killed mutations
198
186
  #
199
187
  # @return [Array<Result::Mutation>]
@@ -260,31 +248,30 @@ module Mutant
260
248
 
261
249
  # Mutation result
262
250
  class Mutation
263
- include Result, Anima.new(:runtime, :mutation, :test_results, :index)
251
+ include Result, Anima.new(:mutation, :test_result, :index)
264
252
 
265
- # Test if mutation was handled successfully
253
+ # Return runtime
266
254
  #
267
- # @return [Boolean]
255
+ # @return [Float]
268
256
  #
269
257
  # @api private
270
258
  #
271
- def success?
272
- test_results.any?(&:success?)
259
+ def runtime
260
+ test_result.runtime
273
261
  end
274
262
 
275
- # Return failed test results
263
+ alias_method :killtime, :runtime
264
+
265
+ # Test if mutation was handled successfully
276
266
  #
277
- # @return [Array]
267
+ # @return [Boolean]
278
268
  #
279
269
  # @api private
280
270
  #
281
- def failed_test_results
282
- test_results.select(&:fail?)
271
+ def success?
272
+ mutation.class.success?(test_result)
283
273
  end
284
274
 
285
- sum :killtime, :test_results
286
-
287
275
  end # Mutation
288
-
289
276
  end # Result
290
277
  end # Mutant
data/lib/mutant/runner.rb CHANGED
@@ -3,163 +3,78 @@ module Mutant
3
3
  class Runner
4
4
  include Adamantium::Flat, Concord.new(:env), Procto.call(:result)
5
5
 
6
+ # Status of the runner execution
7
+ class Status
8
+ include Adamantium, Anima::Update, Anima.new(
9
+ :env_result,
10
+ :active_jobs,
11
+ :done
12
+ )
13
+ end # Status
14
+
15
+ # Job to push to workers
16
+ class Job
17
+ include Adamantium::Flat, Anima.new(:index, :mutation)
18
+ end # Job
19
+
20
+ # Job result object received from workers
21
+ class JobResult
22
+ include Adamantium::Flat, Anima.new(:job, :result)
23
+ end
24
+
25
+ REPORT_FREQUENCY = 20.0
26
+ REPORT_DELAY = 1 / REPORT_FREQUENCY
27
+
6
28
  # Initialize object
7
29
  #
8
30
  # @return [undefined]
9
31
  #
10
32
  # @api private
11
33
  #
12
- def initialize(env)
34
+ def initialize(*)
13
35
  super
14
36
 
15
- @collector = Collector.new(env)
16
- @mutex = Mutex.new
17
- @mutations = env.mutations.dup
18
- @index = 0
19
- @continue = true
20
-
21
- config.integration.setup
22
-
23
37
  reporter.start(env)
38
+ config.integration.setup
24
39
 
25
- run
26
-
27
- @result = @collector.result
28
-
29
- reporter.report(result)
30
- end
31
-
32
- # Return result
33
- #
34
- # @return [Result::Env]
35
- #
36
- # @api private
37
- #
38
- attr_reader :result
39
-
40
- private
40
+ @master = config.actor_env.current.bind(Master.call(env))
41
41
 
42
- # Run mutation analysis
43
- #
44
- # @return [Report::Subject]
45
- #
46
- # @api private
47
- #
48
- def run
49
- Parallel.map(
50
- method(:next),
51
- in_threads: config.jobs,
52
- finish: method(:finish),
53
- start: method(:start),
54
- &method(:run_mutation)
55
- )
56
- end
42
+ status = nil
57
43
 
58
- # Return next mutation or stop
59
- #
60
- # @return [Mutation]
61
- # in case there is a next mutation
62
- #
63
- # @return [Parallel::Stop]
64
- # in case there is no next mutation or runner should stop early
65
- #
66
- #
67
- # @api private
68
- def next
69
- @mutex.synchronize do
70
- mutation = @mutations.at(@index)
71
- if @continue && mutation
72
- @index += 1
73
- mutation
74
- else
75
- Parallel::Stop
76
- end
44
+ loop do
45
+ status = current_status
46
+ break if status.done
47
+ reporter.progress(status)
48
+ Kernel.sleep(REPORT_DELAY)
77
49
  end
78
- end
79
50
 
80
- # Handle started mutation
81
- #
82
- # @param [Mutation] mutation
83
- # @param [Fixnum] _index
84
- #
85
- # @return [undefined]
86
- #
87
- # @api private
88
- #
89
- def start(mutation, _index)
90
- @mutex.synchronize do
91
- @collector.start(mutation)
92
- end
93
- end
51
+ reporter.progress(status)
94
52
 
95
- # Handle finished mutation
96
- #
97
- # @param [Mutation] mutation
98
- # @param [Fixnum] index
99
- # @param [Object] result
100
- #
101
- # @return [undefined]
102
- #
103
- # @api private
104
- #
105
- def finish(mutation, index, result)
106
- return unless result.is_a?(Mutant::Result::Mutation)
53
+ @master.call(:stop)
107
54
 
108
- test_results = result.test_results.zip(mutation.subject.tests).map do |test_result, test|
109
- test_result.update(test: test, mutation: mutation) if test_result
110
- end.compact
55
+ @result = status.env_result
111
56
 
112
- @mutex.synchronize do
113
- process_result(result.update(index: index, mutation: mutation, test_results: test_results))
114
- end
57
+ reporter.report(@result)
115
58
  end
116
59
 
117
- # Process result
118
- #
119
- # @param [Result::Mutation] result
60
+ # Return result
120
61
  #
121
- # @return [undefined]
62
+ # @return [Result::Env]
122
63
  #
123
64
  # @api private
124
65
  #
125
- def process_result(result)
126
- @collector.finish(result)
127
- reporter.progress(@collector)
128
- return unless config.fail_fast && !result.success?
129
- @continue = false
130
- end
66
+ attr_reader :result
131
67
 
132
- # Run mutation
133
- #
134
- # @param [Mutation] mutation
135
- #
136
- # @return [Report::Mutation]
137
- #
138
- # @api private
139
- #
140
- def run_mutation(mutation)
141
- Result::Mutation.compute do
142
- {
143
- index: nil,
144
- mutation: nil,
145
- test_results: kill_mutation(mutation)
146
- }
147
- end
148
- end
68
+ private
149
69
 
150
- # Kill mutation
151
- #
152
- # @param [Mutation] mutation
70
+ # Return reporter
153
71
  #
154
- # @return [Array<Result::Test>]
72
+ # @return [Reporter]
155
73
  #
156
74
  # @api private
157
75
  #
158
- def kill_mutation(mutation)
159
- mutation.subject.tests.each_with_object([]) do |test, results|
160
- results << result = run_mutation_test(mutation, test)
161
- return results if mutation.killed_by?(result)
162
- end
76
+ def reporter
77
+ env.config.reporter
163
78
  end
164
79
 
165
80
  # Return config
@@ -172,36 +87,14 @@ module Mutant
172
87
  env.config
173
88
  end
174
89
 
175
- # Return test result
176
- #
177
- # @return [Report::Test]
178
- #
179
- # @api private
180
- #
181
- def run_mutation_test(mutation, test)
182
- time = Time.now
183
- config.isolation.call do
184
- mutation.insert
185
- test.run
186
- end
187
- rescue Isolation::Error => exception
188
- Result::Test.new(
189
- test: test,
190
- mutation: mutation,
191
- runtime: Time.now - time,
192
- output: exception.message,
193
- passed: false
194
- )
195
- end
196
-
197
- # Return reporter
90
+ # Return current status
198
91
  #
199
- # @return [Reporter]
92
+ # @return [Status]
200
93
  #
201
94
  # @api private
202
95
  #
203
- def reporter
204
- config.reporter
96
+ def current_status
97
+ @master.call(:status)
205
98
  end
206
99
 
207
100
  end # Runner
@@ -0,0 +1,174 @@
1
+ module Mutant
2
+ class Runner
3
+ # Master actor to control workers
4
+ class Master
5
+ include Concord.new(:env, :actor)
6
+
7
+ private_class_method :new
8
+
9
+ # Run master runner component
10
+ #
11
+ # @param [Env] env
12
+ #
13
+ # @return [Actor::Sender]
14
+ #
15
+ # @api private
16
+ #
17
+ def self.call(env)
18
+ env.config.actor_env.spawn do |actor|
19
+ new(env, actor).__send__(:run)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # Initialize object
26
+ #
27
+ # @return [undefined]
28
+ #
29
+ # @api private
30
+ #
31
+ def initialize(*)
32
+ super
33
+
34
+ @scheduler = Scheduler.new(env)
35
+ @workers = env.config.jobs
36
+ @stop = false
37
+ @stopping = false
38
+ end
39
+
40
+ # Run work loop
41
+ #
42
+ # @return [self]
43
+ #
44
+ # @api private
45
+ #
46
+ def run
47
+ @workers.times do |id|
48
+ Worker.run(
49
+ id: id,
50
+ config: env.config,
51
+ parent: actor.sender
52
+ )
53
+ end
54
+
55
+ receive_loop
56
+ end
57
+
58
+ # Handle messages
59
+ #
60
+ # @param [Actor::Message] message
61
+ #
62
+ # @return [undefined]
63
+ #
64
+ # @api private
65
+ #
66
+ def handle(message)
67
+ type, payload = message.type, message.payload
68
+ case type
69
+ when :ready
70
+ ready_worker(payload)
71
+ when :status
72
+ handle_status(payload)
73
+ when :result
74
+ handle_result(payload)
75
+ when :stop
76
+ handle_stop(payload)
77
+ else
78
+ fail Actor::ProtocolError, "Unexpected message: #{type.inspect}"
79
+ end
80
+ end
81
+
82
+ # Run receive loop
83
+ #
84
+ # @return [undefined]
85
+ #
86
+ # @api private
87
+ #
88
+ def receive_loop
89
+ loop do
90
+ break if @workers.zero? && @stop
91
+ handle(actor.receiver.call)
92
+ end
93
+ end
94
+
95
+ # Handle status
96
+ #
97
+ # @param [Actor::Sender] sender
98
+ #
99
+ # @return [undefined]
100
+ #
101
+ # @api private
102
+ #
103
+ def handle_status(sender)
104
+ sender.call(Actor::Message.new(:status, @scheduler.status))
105
+ end
106
+
107
+ # Handle result
108
+ #
109
+ # @param [JobResult] job_result
110
+ #
111
+ # @return [undefined]
112
+ #
113
+ # @api private
114
+ #
115
+ def handle_result(job_result)
116
+ return if @stopping
117
+ @scheduler.job_result(job_result)
118
+ @stopping = env.config.fail_fast && @scheduler.status.done
119
+ end
120
+
121
+ # Handle stop
122
+ #
123
+ # @param [Actor::Sender] sender
124
+ #
125
+ # @return [undefined]
126
+ #
127
+ # @api private
128
+ #
129
+ def handle_stop(sender)
130
+ @stopping = true
131
+ @stop = true
132
+ receive_loop
133
+ sender.call(Actor::Message.new(:stop))
134
+ end
135
+
136
+ # Handle ready worker
137
+ #
138
+ # @param [Actor::Sender] sender
139
+ #
140
+ # @return [undefined]
141
+ #
142
+ # @api private
143
+ #
144
+ def ready_worker(sender)
145
+ if @stopping
146
+ stop_worker(sender)
147
+ return
148
+ end
149
+
150
+ job = @scheduler.next_job
151
+
152
+ if job
153
+ sender.call(Actor::Message.new(:job, job))
154
+ else
155
+ stop_worker(sender)
156
+ end
157
+ end
158
+
159
+ # Stop worker
160
+ #
161
+ # @param [Actor::Sender] sender
162
+ #
163
+ # @return [undefined]
164
+ #
165
+ # @api private
166
+ #
167
+ def stop_worker(sender)
168
+ @workers -= 1
169
+ sender.call(Actor::Message.new(:stop))
170
+ end
171
+
172
+ end # Master
173
+ end # Runner
174
+ end # Mutant