mutant 0.6.7 → 0.7.1

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